diff --git a/lib/miosix-kernel/README.md b/lib/miosix-kernel/README.md new file mode 100644 index 00000000..f9d5577f --- /dev/null +++ b/lib/miosix-kernel/README.md @@ -0,0 +1,5 @@ + +This repository contains an adapted version of the miosix kernel, tailored for the usage with the OpenRTX targets equipped with an ARM cortex M4 microcontroller. + +The original repository can be found here: [https://github.com/fedetft/miosix-kernel](https://github.com/fedetft/miosix-kernel) + diff --git a/lib/miosix-kernel/meson.build b/lib/miosix-kernel/meson.build new file mode 100644 index 00000000..97f95692 --- /dev/null +++ b/lib/miosix-kernel/meson.build @@ -0,0 +1,28 @@ + +miosix_inc = ['lib/miosix-kernel/miosix', + 'lib/miosix-kernel/miosix/config', + 'lib/miosix-kernel/miosix/arch/common', + 'lib/miosix-kernel/miosix/util'] + +miosix_src = ['lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.cpp', + 'lib/miosix-kernel/miosix/kernel/error.cpp', + 'lib/miosix-kernel/miosix/kernel/kernel.cpp', + 'lib/miosix-kernel/miosix/kernel/pthread.cpp', + 'lib/miosix-kernel/miosix/kernel/stage_2_boot.cpp', + 'lib/miosix-kernel/miosix/kernel/sync.cpp', + 'lib/miosix-kernel/miosix/kernel/timeconversion.cpp', + 'lib/miosix-kernel/miosix/util/util.cpp', + 'lib/miosix-kernel/miosix/stdlib_integration/libc_integration.cpp', + 'lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.cpp'] + +miosix_def = {'DONT_USE_CMSIS_INIT' : '', + 'COMPILING_MIOSIX' : '', + '_POSIX_PRIORITY_SCHEDULING': ''} + +## +## ARM Cortex M4 +## +miosix_cm4f_inc = miosix_inc + ['lib/miosix-kernel/miosix/arch/cortexM4F'] +miosix_cm4f_src = miosix_src + ['lib/miosix-kernel/miosix/arch/cortexM4F/portability.cpp', + 'lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.cpp'] +miosix_cm4f_def = miosix_def + {'_ARCH_CORTEXM4' : ''} diff --git a/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_arm7.h b/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_arm7.h new file mode 100644 index 00000000..02a25a09 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_arm7.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2013, 2014, 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ATOMIC_OPS_IMPL_H +#define ATOMIC_OPS_IMPL_H + +namespace miosix { + +inline int atomicSwap(volatile int *p, int v) +{ + //This is the only atomic operation in the ARM7 assembler + register int result; + asm volatile("swp %0, %1, [%2]" : "=&r"(result) : "r"(v),"r"(p) : "memory"); + return result; +} + +inline void atomicAdd(volatile int *p, int incr) +{ + register int a,b; //Temporaries used by ASM code + asm volatile(" mrs %0, cpsr \n" + " tst %0, #0x80 \n" + " orreq %1, %0, #0x80 \n" + " msreq cpsr_c, %1 \n" + " ldr %1, [%3] \n" + " add %1, %1, %2 \n" + " str %1, [%3] \n" + " tst %0, #0x80 \n" + " msreq cpsr_c, %0 \n" + : "=&r"(a),"=&r"(b) + : "r"(incr),"r"(p) + : "cc","memory"); +} + +inline int atomicAddExchange(volatile int *p, int incr) +{ + register int a; //Temporaries used by ASM code + register int result; + asm volatile(" mrs %0, cpsr \n" + " tst %0, #0x80 \n" + " orreq %1, %0, #0x80 \n" + " msreq cpsr_c, %1 \n" + " ldr %1, [%3] \n" + " add %2, %2, %1 \n" + " str %2, [%3] \n" + " tst %0, #0x80 \n" + " msreq cpsr_c, %0 \n" + : "=&r"(a),"=&r"(result),"+&r"(incr)//Incr is read and clobbered + : "r"(p) + : "cc","memory"); + return result; +} + +inline int atomicCompareAndSwap(volatile int *p, int prev, int next) +{ + register int a; //Temporaries used by ASM code + register int result; + asm volatile(" mrs %0, cpsr \n" + " tst %0, #0x80 \n" + " orreq %1, %0, #0x80 \n" + " msreq cpsr_c, %1 \n" + " ldr %1, [%2] \n" + " cmp %1, %3 \n" + " streq %4, [%2] \n" + " tst %0, #0x80 \n" + " msreq cpsr_c, %0 \n" + : "=&r"(a),"=&r"(result) + : "r"(p),"r"(prev),"r"(next) + : "cc","memory"); + return result; +} + +inline void *atomicFetchAndIncrement(void * const volatile * p, int offset, + int incr) +{ + register int a,b; //Temporaries used by ASM code + register void *result; + asm volatile(" mrs %0, cpsr \n" + " tst %0, #0x80 \n" + " orreq %1, %0, #0x80 \n" + " msreq cpsr_c, %1 \n" + " ldr %2, [%3] \n" + " ldr %1, [%2, %4, asl #2] \n" + " add %1, %1, %5 \n" + " str %1, [%2, %4, asl #2] \n" + " tst %0, #0x80 \n" + " msreq cpsr_c, %0 \n" + : "=&r"(a),"=&r"(b),"=&r"(result) + : "r"(p),"r"(offset),"r"(incr) + : "cc","memory"); + return result; +} + +} //namespace miosix + +#endif //ATOMIC_OPS_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_cortexM0.h b/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_cortexM0.h new file mode 100644 index 00000000..f0d5b700 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_cortexM0.h @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2013, 2014, 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ATOMIC_OPS_IMPL_M0_H +#define ATOMIC_OPS_IMPL_M0_H + +/** + * Cortex M0/M0+ architectures does not support __LDREXW, __STREXW and __CLREX + * instructions, so we have to redefine the atomic operations using functions + * that disable the interrupts. + * + * TODO: actually this implementation is not very efficient + * + */ + +#include "interfaces/arch_registers.h" +#include + +namespace miosix { + + +inline int atomicSwap(volatile int *p, int v) +{ + InterruptDisableLock dLock; + + int result = *p; + *p = v; + return result; +} + +inline void atomicAdd(volatile int *p, int incr) +{ + InterruptDisableLock dLock; + + *p += incr; +} + +inline int atomicAddExchange(volatile int *p, int incr) +{ + InterruptDisableLock dLock; + + int result = *p; + *p += incr; + return result; +} + +inline int atomicCompareAndSwap(volatile int *p, int prev, int next) +{ + InterruptDisableLock dLock; + + int result = *p; + if(*p == prev) *p = next; + return result; +} + +inline void *atomicFetchAndIncrement(void * const volatile * p, int offset, + int incr) +{ + InterruptDisableLock dLock; + + volatile uint32_t *pt; + + void *result = *p; + if(result == 0) return 0; + pt = reinterpret_cast(result) + offset; + *pt += incr; + return result; +} + +} //namespace miosix + +#endif //ATOMIC_OPS_IMPL_M0_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_cortexMx.h b/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_cortexMx.h new file mode 100644 index 00000000..525829c1 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/atomic_ops_impl_cortexMx.h @@ -0,0 +1,107 @@ +/*************************************************************************** + * Copyright (C) 2013, 2014, 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ATOMIC_OPS_IMPL_H +#define ATOMIC_OPS_IMPL_H + +#include "interfaces/arch_registers.h" + +namespace miosix { + +inline int atomicSwap(volatile int *p, int v) +{ + int result; + volatile uint32_t *ptr=reinterpret_cast(p); + do { + result=__LDREXW(ptr); + } while(__STREXW(v,ptr)); + asm volatile("":::"memory"); + return result; +} + +inline void atomicAdd(volatile int *p, int incr) +{ + int value; + volatile uint32_t *ptr=reinterpret_cast(p); + do { + value=__LDREXW(ptr); + } while(__STREXW(value+incr,ptr)); + asm volatile("":::"memory"); +} + +inline int atomicAddExchange(volatile int *p, int incr) +{ + int result; + volatile uint32_t *ptr=reinterpret_cast(p); + do { + result=__LDREXW(ptr); + } while(__STREXW(result+incr,ptr)); + asm volatile("":::"memory"); + return result; +} + +inline int atomicCompareAndSwap(volatile int *p, int prev, int next) +{ + int result; + volatile uint32_t *ptr=reinterpret_cast(p); + do { + result=__LDREXW(ptr); + if(result!=prev) + { + __CLREX(); + return result; + } + } while(__STREXW(next,ptr)); + asm volatile("":::"memory"); + return result; +} + +inline void *atomicFetchAndIncrement(void * const volatile * p, int offset, + int incr) +{ + void *result; + volatile uint32_t *rcp; + int rc; + do { + for(;;) + { + result=*p; + if(result==0) return 0; + rcp=reinterpret_cast(result)+offset; + rc=__LDREXW(rcp); + asm volatile("":::"memory"); + if(result==*p) break; + __CLREX(); + } + } while(__STREXW(rc+incr,rcp)); + asm volatile("":::"memory"); + return result; +} + +} //namespace miosix + +#endif //ATOMIC_OPS_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/cache_cortexMx.cpp b/lib/miosix-kernel/miosix/arch/common/core/cache_cortexMx.cpp new file mode 100644 index 00000000..1330b247 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/cache_cortexMx.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "cache_cortexMx.h" +#include "mpu_cortexMx.h" +#include + +using namespace std; + +namespace miosix { + +static const unsigned int cacheLine=32; //Cortex-M7 cache line size + +/** + * Using the MPU, configure a region of the memory space as + * - write-through cacheable + * - non-shareable + * - readable/writable/executable only by privileged code (for compatibility + * with the way processes use the MPU) + * \param region MPU region. Note that region 6 and 7 are used by processes, and + * should be avoided here + * \param base base address, aligned to a 32Byte cache line + * \param size size, must be at least 32 and a power of 2, or it is rounded to + * the next power of 2 + */ +static void IRQconfigureCacheability(unsigned int region, unsigned int base, + unsigned int size) +{ + // NOTE: The ARM documentation is unclear about the effect of the shareable + // bit on a single core architecture. Experimental evidence on an STM32F476 + // shows that setting it in IRQconfigureCache for the internal RAM region + // causes the boot to fail. + // For this reason, all regions are marked as not shareable + MPU->RBAR=(base & (~(cacheLine-1))) | MPU_RBAR_VALID_Msk | region; + MPU->RASR=1<(xramBase),xramSize); + IRQenableMPUatBoot(); + + SCB_EnableICache(); + SCB_EnableDCache(); +} + +#if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT==1) +/** + * Align a generic buffer to another one that contains the first one, but the + * start size is aligned to a cache line + */ +static pair alignBuffer(void *buffer, int size) +{ + auto bufferAddr=reinterpret_cast(buffer); + + auto base=bufferAddr & (~(cacheLine-1)); + size+=bufferAddr-base; + + return make_pair(reinterpret_cast(base),size); +} + +void markBufferAfterDmaRead(void *buffer, int size) +{ + //Since the current cache policy is write-through, we just invalidate the + //cache lines corresponding to the buffer. No need to flush (clean) the cache. + auto result=alignBuffer(buffer,size); + SCB_InvalidateDCache_by_Addr(result.first,result.second); +} +#endif + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/core/cache_cortexMx.h b/lib/miosix-kernel/miosix/arch/common/core/cache_cortexMx.h new file mode 100644 index 00000000..2c0e6667 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/cache_cortexMx.h @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef CACHE_CORTEX_MX_H +#define CACHE_CORTEX_MX_H + +/* + * README: Essentials about how cache support is implemented. + * + * Caches in the Cortex M7 are transparent to software, except when using + * the DMA. As the DMA reads and writes directly to memory, explicit management + * is required. The default cache policy is write-back, but this has been deemed + * unsuitable for Miosix, so for the time being only write-through is supported. + * + * The IRQconfigureCache() configures the cache as write-through and enables it. + * It should be called early at boot, in stage_1_boot.cpp + * + * When writing DMA drivers, before passing a buffer to the DMA for it to be + * written to a peripheral, call markBufferBeforeDmaWrite(). + * After a DMA read from a peripheral to a memory buffer has completed, + * call markBufferAfterDmaRead(). These take care of keeping the DMA operations + * in sync with the cache. These become no-ops for other architectures, so you + * can freely put the in any driver. + */ + +/* + * Some more info about caches. Why not supporting write-back? + * When writing data from memory to a peripheral using DMA, things are easy + * also with write-back. You just flush (clean) the relevant cache lines, and + * the DMA has access to the correct values. So it looks like it's ok. + * When instead the DMA has to write to a memory location things become + * complicated. Assume that a buffer not aligned to a cache line is passed to + * a DMA read routine. After that a context switch happens and another thread + * writes to a memory location that is on the same cache line as the (beginning + * or end of) the buffer passed to the DMA. At the same time the DMA is writing + * to the buffer. + * At the end the situation looks like this, where the thread has written to + * location X in the cache, while the DMA has written Y to the buffer. + * <-- outside buffer --x-- buffer --> + * +----------------------------------+ + * | X | | cache + * +----------------------------------+ + * | |YYYYYYYYYYYYY| memory + * +----------------------------------+ + * What are you suppose to do? If you flush (clean) the cache line, X will be + * committed to memory, but the Y data written by the DMA will be lost. If you + * invalidate the cache, Y is preserved, but X is lost. + * If you're just thinking that the problem can be solved by making sure buffers + * are aligned to the cache line (and their size is a multiple of the cache + * line), well, there's a problem. + * Miosix is a real-time OS, and for performance and safety, most drivers are + * zero copy. Applications routinely pass to DMA drivers such as the SDIO large + * buffers (think 100+KB). Of course allocating an aligned buffer inside the + * DMA driver as large as the user-passed buffer and copying the data there + * isn't just slow, it wouldn't be safe, as you risk to exceed the free heap + * memory or fragment the heap. Allocating a small buffer and splitting the + * large DMA transfer in many small ones where the user passed buffer is copyied + * one chunk at a time would be feasible, but even slower, and even more so + * considering that some peripherals such as SD cards are optimized for large + * sequential writes, not for small chunks. + * But what if we make sure all buffers passed to DMA drivers are aligned? + * That is a joke, as it the burden of doing so is unmaintainable. Buffers + * passed to DMA memory are everywhere, in the C/C++ standard library + * (think the buffer used for IO formatting inside printf/fprintf), and + * everywherein application code. Something like + * char s[128]="Hello world"; + * puts(s); + * may cause s to be passed to a DMA driver. We would spend our lives chasing + * unaligned buffers, and the risk of this causing difficult to reproduce memory + * corruptions is too high. For this reason, for the time being, Miosix only + * supports write-through caching on the Cortex-M7. + * + * A note about performance. Using the testsuite benchmark, when caches are + * disabled the STM32F746 @ 216MHz is less than half the speed of the + * STM32F407 @ 168MHz. By enabling the ICACHE things get better, but it is + * still slower, and achieves a speedup of 1.53 only when both ICACHE and + * DCACHE are enabled. The speedup also includes the gains due to the faster + * clock frequency. So if you want speed you have to use caches. + */ + +#include "interfaces/arch_registers.h" + +namespace miosix { + +/** + * To be called in stage_1_boot.cpp to configure caches. + * Only call this function if the board has caches. + * \param xramBase base address of external memory, if present, otherwise nullptr + * \param xramSize size of external memory, if present, otherwise 0 + */ +void IRQconfigureCache(const unsigned int *xramBase=nullptr, unsigned int xramSize=0); + +/** + * Call this function to mark a buffer before starting a DMA transfer where + * the DMA will read the buffer. + * \param buffer buffer + * \param size buffer size + */ +inline void markBufferBeforeDmaWrite(const void *buffer, int size) +{ +#if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT==1) + // You may think that since the cache is configured as write-through, + // there's nothing to do before the DMA can read a memory buffer just + // written by the CPU, right? Wrong! Other than the cache, there's the + // write buffer to worry about. My hypothesis is that once a memory region + // is marked as cacheable, the write buffer becomes more lax in + // automatically flushing as soon as possible. In the stm32 serial port + // driver writing just a few characters causes garbage to be printed if + // this __DSB() is removed. Apparently, the characters remian in the write + // buffer. + __DSB(); +#endif +} + +/** + * Call this function after having completed a DMA transfer where the DMA has + * written to the buffer. + * \param buffer buffer + * \param size buffer size + */ +#if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT==1) +void markBufferAfterDmaRead(void *buffer, int size); +#else +inline void markBufferAfterDmaRead(void *buffer, int size) {} +#endif + +} //namespace miosix + +#endif //CACHE_CORTEX_MX_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/endianness_impl_arm7.h b/lib/miosix-kernel/miosix/arch/common/core/endianness_impl_arm7.h new file mode 100644 index 00000000..0c96ef21 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/endianness_impl_arm7.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ENDIANNESS_IMPL_H +#define ENDIANNESS_IMPL_H + +#ifndef MIOSIX_BIG_ENDIAN +//This target is little endian +#define MIOSIX_LITTLE_ENDIAN +#endif //MIOSIX_BIG_ENDIAN + +#ifdef __cplusplus +#define __MIOSIX_INLINE inline +#else //__cplusplus +#define __MIOSIX_INLINE static inline +#endif //__cplusplus + + +__MIOSIX_INLINE unsigned short swapBytes16(unsigned short x) +{ + return (x>>8) | (x<<8); +} + +__MIOSIX_INLINE unsigned int swapBytes32(unsigned int x) +{ + #ifdef __GNUC__ + return __builtin_bswap32(x); + #else //__GNUC__ + return ( x>>24) | + ((x<< 8) & 0x00ff0000) | + ((x>> 8) & 0x0000ff00) | + ( x<<24); + #endif //__GNUC__ +} + +__MIOSIX_INLINE unsigned long long swapBytes64(unsigned long long x) +{ + #ifdef __GNUC__ + return __builtin_bswap64(x); + #else //__GNUC__ + return ( x>>56) | + ((x<<40) & 0x00ff000000000000ull) | + ((x<<24) & 0x0000ff0000000000ull) | + ((x<< 8) & 0x000000ff00000000ull) | + ((x>> 8) & 0x00000000ff000000ull) | + ((x>>24) & 0x0000000000ff0000ull) | + ((x>>40) & 0x000000000000ff00ull) | + ( x<<56); + #endif //__GNUC__ +} + +#undef __MIOSIX_INLINE + +#endif //ENDIANNESS_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/endianness_impl_cortexMx.h b/lib/miosix-kernel/miosix/arch/common/core/endianness_impl_cortexMx.h new file mode 100644 index 00000000..6a656ea9 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/endianness_impl_cortexMx.h @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (C) 2011, 2012, 2013, 2014, 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ENDIANNESS_IMPL_H +#define ENDIANNESS_IMPL_H + +#ifndef MIOSIX_BIG_ENDIAN +//This target is little endian +#define MIOSIX_LITTLE_ENDIAN +#endif //MIOSIX_BIG_ENDIAN + +#ifdef __cplusplus +#define __MIOSIX_INLINE inline +#else //__cplusplus +#define __MIOSIX_INLINE static inline +#endif //__cplusplus + +__MIOSIX_INLINE unsigned short swapBytes16(unsigned short x) +{ + //It's kind of a shame that GCC can't automatically make use of + //instructions like rev and rev16 to do byte swapping. + //Moreover, while for 32 and 64 bit integers it has builtins, for 16 bit + //we're forced to use inline asm. + #ifdef __GNUC__ + if(!__builtin_constant_p(x)) + { + unsigned short y; + asm("rev16 %0, %1":"=r"(y):"r"(x)); + return y; + } else { + //It gets worse: if value is constant inlining assembler disables + //contant folding, wtf... + return (x>>8) | (x<<8); + } + #else + return (x>>8) | (x<<8); + #endif +} + +__MIOSIX_INLINE unsigned int swapBytes32(unsigned int x) +{ + #ifdef __GNUC__ + return __builtin_bswap32(x); + #else + return ( x>>24) | + ((x<< 8) & 0x00ff0000) | + ((x>> 8) & 0x0000ff00) | + ( x<<24); + #endif +} + +__MIOSIX_INLINE unsigned long long swapBytes64(unsigned long long x) +{ + #ifdef __GNUC__ + return __builtin_bswap64(x); + #else + return ( x>>56) | + ((x<<40) & 0x00ff000000000000ull) | + ((x<<24) & 0x0000ff0000000000ull) | + ((x<< 8) & 0x000000ff00000000ull) | + ((x>> 8) & 0x00000000ff000000ull) | + ((x>>24) & 0x0000000000ff0000ull) | + ((x>>40) & 0x000000000000ff00ull) | + ( x<<56); + #endif +} + +#undef __MIOSIX_INLINE + +#endif //ENDIANNESS_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/interrupts.h b/lib/miosix-kernel/miosix/arch/common/core/interrupts.h new file mode 100644 index 00000000..99089905 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/interrupts.h @@ -0,0 +1,19 @@ + +//Interrupt code is common for all the cortex M cores, so it has been put here + +#ifdef _ARCH_ARM7_LPC2000 +#include "interrupts_arm7.h" +#elif defined(_ARCH_CORTEXM0) || defined(_ARCH_CORTEXM3) \ + || defined(_ARCH_CORTEXM4) || defined(_ARCH_CORTEXM7) +#include "interrupts_cortexMx.h" +#else +#error "Unknown arch" +#endif + +// Cortex M0 and M0+ does not have some SCB registers, in order to avoid +// compilation issues a flag is defined to disable code that accesses to +// registers not present in these families + +#if defined(_ARCH_CORTEXM0_STM32) +#define _ARCH_CORTEXM0 +#endif diff --git a/lib/miosix-kernel/miosix/arch/common/core/interrupts_arm7.cpp b/lib/miosix-kernel/miosix/arch/common/core/interrupts_arm7.cpp new file mode 100644 index 00000000..44b8fae9 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/interrupts_arm7.cpp @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "kernel/logging.h" +#include "interfaces/portability.h" +#include "config/miosix_settings.h" +#include "interrupts.h" + +using namespace miosix; + +//Look up table used by printUnsignedInt() +static const char hexdigits[]="0123456789abcdef"; + +/** + * \internal + * Used to print an unsigned int in hexadecimal format, and to reboot the system + * Note that printf/iprintf cannot be used inside an IRQ, so that's why there's + * this function. + * \param x number to print + */ +static void printUnsignedInt(unsigned int x) +{ + char result[]="0x........\r\n"; + for(int i=9;i>=2;i--) + { + result[i]=hexdigits[x & 0xf]; + x>>=4; + } + IRQerrorLog(result); +} + +/** + * \internal + * Spurious interrupt handler. + * The LPC2138 datasheet says that spurious interrups can occur, but until now + * it never happened. If and when spurious interruts will occur, this code will + * be modified to deal with them. Until then, this code just reboots the system. + */ +void default_IRQ_Routine() +{ + IRQerrorLog("\r\n***Unexpected IRQ\r\n"); + miosix_private::IRQsystemReboot(); +} + +/** + * \internal + * FIQ is currently not used. + * Prints an error message, and reboots the system. + * Stack usage is 24 Bytes (measured with watermarking and stack dump) + * so a 32 byte stack is used (to leave some guard space). + * If the user wants to use FIQ, it is important to remember to increase the + * FIQ's stack size, which is defined in miosix.ld + */ +extern "C" void FIQ_Routine() +{ + IRQerrorLog("\r\n***Unexpected FIQ\r\n"); + miosix_private::IRQsystemReboot(); +} + +/** + * \internal + * This ISR handles Undefined instruction. + * Prints an error message, showing an address near the instruction that caused + * the exception. This address together with the map file allows finding the + * function that caused the exception. + * Please note that when compiling with some level of optimization, the compiler + * can inline functions so the address is no longer accurate. + * Stack usage is 47 Bytes (measured with watermarking and stack dump) + * so a 48 byte stack is used (stak must be word-aligned). + */ +extern "C" void UNDEF_Routine() +{ + //These two instructions MUST be the first two instructions of the interrupt + //routine. They store in return_address the pc of the instruction that + //caused the interrupt. + register int returnAddress; + asm volatile("mov %0, lr" : "=r"(returnAddress)); + + IRQerrorLog("\r\n***Unexpected UNDEF @ "); + printUnsignedInt(returnAddress); + miosix_private::IRQsystemReboot(); +} + +/** + * \internal + * This ISR handles data abort. + * Prints an error message, showing an address near the instruction that caused + * the exception. This address together with the map file allows finding the + * function that caused the exception. + * Please note that when compiling with some level of optimization, the compiler + * can inline functions so the address is no longer accurate. + * Stack usage is 47 Bytes (measured with watermarking and stack dump) + * so a 48 byte stack is used (stak must be word-aligned). + */ +extern "C" void DABT_Routine() +{ + //These two instructions MUST be the first two instructions of the interrupt + //routine. They store in return_address the pc of the instruction that + //caused the interrupt. (lr has an offset of 8 during a data abort) + register int returnAddress; + asm volatile("sub %0, lr, #8" : "=r"(returnAddress)); + + IRQerrorLog("\r\n***Unexpected data abort @ "); + printUnsignedInt(returnAddress); + miosix_private::IRQsystemReboot(); +} + +/** + * \internal + * This ISR handles prefetch abort. + * Prints an error message, showing an address near the instruction that caused + * the exception. This address together with the map file allows finding the + * function that caused the exception. + * Please note that when compiling with some level of optimization, the compiler + * can inline functions so the address is no longer accurate. + * Stack usage is 47 Bytes (measured with watermarking and stack dump) + * so a 48 byte stack is used (stak must be word-aligned). + */ +extern "C" void PABT_Routine() +{ + //These two instructions MUST be the first two instructions of the interrupt + //routine. They store in return_address the pc of the instruction that + //caused the interrupt. (lr has an offset of 4 during a data abort) + register int returnAddress; + asm volatile("sub %0, lr, #4" : "=r"(returnAddress)); + + IRQerrorLog("\r\n***Unexpected prefetch abort @ "); + printUnsignedInt(returnAddress); + miosix_private::IRQsystemReboot(); +} diff --git a/lib/miosix-kernel/miosix/arch/common/core/interrupts_arm7.h b/lib/miosix-kernel/miosix/arch/common/core/interrupts_arm7.h new file mode 100644 index 00000000..743731b7 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/interrupts_arm7.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2008 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +/*********************************************************************** +* interrupts.h Part of the Miosix Embedded OS. +* Implementation of "Generic" interrupts, not related to a particular +* hardware driver. +************************************************************************/ + +#ifndef INTERRUPTS_H +#define INTERRUPTS_H + +void default_IRQ_Routine() __attribute__ ((interrupt("IRQ"))); +extern "C" void FIQ_Routine() __attribute__ ((interrupt("FIQ"))); +extern "C" void UNDEF_Routine() __attribute__ ((interrupt("UNDEF"))); +extern "C" void DABT_Routine() __attribute__ ((interrupt("DABT"))); +extern "C" void PABT_Routine() __attribute__ ((interrupt("PABT"))); + +#endif //INTERRUPTS_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.cpp b/lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.cpp new file mode 100644 index 00000000..dd05ebde --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.cpp @@ -0,0 +1,282 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "kernel/logging.h" +#include "kernel/kernel.h" +#include "config/miosix_settings.h" +#include "interfaces/portability.h" +#include "interfaces/arch_registers.h" +#include "interrupts.h" + +using namespace miosix; + +#ifdef WITH_ERRLOG + +/** + * \internal + * Used to print an unsigned int in hexadecimal format, and to reboot the system + * Note that printf/iprintf cannot be used inside an IRQ, so that's why there's + * this function. + * \param x number to print + */ +static void printUnsignedInt(unsigned int x) +{ + static const char hexdigits[]="0123456789abcdef"; + char result[]="0x........\r\n"; + for(int i=9;i>=2;i--) + { + result[i]=hexdigits[x & 0xf]; + x>>=4; + } + IRQerrorLog(result); +} + +#endif //WITH_ERRLOG + +#if defined(WITH_PROCESSES) || defined(WITH_ERRLOG) + +/** + * \internal + * \return the program counter of the thread that was running when the exception + * occurred. + */ +static unsigned int getProgramCounter() +{ + register unsigned int result; + // Get program counter when the exception was thrown from stack frame + asm volatile("mrs %0, psp \n\t" + "add %0, %0, #24 \n\t" + "ldr %0, [%0] \n\t":"=r"(result)); + return result; +} + +#endif //WITH_PROCESSES || WITH_ERRLOG + +void NMI_Handler() +{ + IRQerrorLog("\r\n***Unexpected NMI\r\n"); + miosix_private::IRQsystemReboot(); +} + +void __attribute__((naked)) HardFault_Handler() +{ + saveContext(); + //Call HardFault_impl(). Name is a C++ mangled name. + asm volatile("bl _Z14HardFault_implv"); + restoreContext(); +} + +void __attribute__((noinline)) HardFault_impl() +{ + #ifdef WITH_PROCESSES + if(miosix::Thread::IRQreportFault(miosix_private::FaultData( + HARDFAULT,getProgramCounter()))) return; + #endif //WITH_PROCESSES + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected HardFault @ "); + printUnsignedInt(getProgramCounter()); + #ifndef _ARCH_CORTEXM0 + unsigned int hfsr=SCB->HFSR; + if(hfsr & 0x40000000) //SCB_HFSR_FORCED + IRQerrorLog("Fault escalation occurred\r\n"); + if(hfsr & 0x00000002) //SCB_HFSR_VECTTBL + IRQerrorLog("A BusFault occurred during a vector table read\r\n"); + #endif //_ARCH_CORTEXM0 + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} + +// Cortex M0/M0+ architecture does not have the interrupts handled by code +// below this point +#ifndef _ARCH_CORTEXM0 + +void __attribute__((naked)) MemManage_Handler() +{ + saveContext(); + //Call MemManage_impl(). Name is a C++ mangled name. + asm volatile("bl _Z14MemManage_implv"); + restoreContext(); +} + +void __attribute__((noinline)) MemManage_impl() +{ + #if defined(WITH_PROCESSES) || defined(WITH_ERRLOG) + unsigned int cfsr=SCB->CFSR; + #endif //WITH_PROCESSES || WITH_ERRLOG + #ifdef WITH_PROCESSES + int id, arg=0; + if(cfsr & 0x00000001) id=MP_XN; + else if(cfsr & 0x00000080) { id=MP; arg=SCB->MMFAR; } + else id=MP_NOADDR; + if(miosix::Thread::IRQreportFault(miosix_private::FaultData( + id,getProgramCounter(),arg))) + { + SCB->SHCSR &= ~(1<<13); //Clear MEMFAULTPENDED bit + return; + } + #endif //WITH_PROCESSES + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected MemManage @ "); + printUnsignedInt(getProgramCounter()); + if(cfsr & 0x00000080) //SCB_CFSR_MMARVALID + { + IRQerrorLog("Fault caused by attempted access to "); + printUnsignedInt(SCB->MMFAR); + } else IRQerrorLog("The address that caused the fault is missing\r\n"); + if(cfsr & 0x00000010) //SCB_CFSR_MSTKERR + IRQerrorLog("Fault occurred during exception stacking\r\n"); + if(cfsr & 0x00000008) //SCB_CFSR_MUNSTKERR + IRQerrorLog("Fault occurred during exception unstacking\r\n"); + if(cfsr & 0x00000002) //SCB_CFSR_DACCVIOL + IRQerrorLog("Fault was caused by invalid PC\r\n"); + if(cfsr & 0x00000001) //SCB_CFSR_IACCVIOL + IRQerrorLog("Fault was caused by attempted execution from XN area\r\n"); + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} + +void __attribute__((naked)) BusFault_Handler() +{ + saveContext(); + //Call BusFault_impl(). Name is a C++ mangled name. + asm volatile("bl _Z13BusFault_implv"); + restoreContext(); +} + +void __attribute__((noinline)) BusFault_impl() +{ + #if defined(WITH_PROCESSES) || defined(WITH_ERRLOG) + unsigned int cfsr=SCB->CFSR; + #endif //WITH_PROCESSES || WITH_ERRLOG + #ifdef WITH_PROCESSES + int id, arg=0; + if(cfsr & 0x00008000) { id=BF; arg=SCB->BFAR; } + else id=BF_NOADDR; + if(miosix::Thread::IRQreportFault(miosix_private::FaultData( + id,getProgramCounter(),arg))) + { + SCB->SHCSR &= ~(1<<14); //Clear BUSFAULTPENDED bit + return; + } + #endif //WITH_PROCESSES + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected BusFault @ "); + printUnsignedInt(getProgramCounter()); + if(cfsr & 0x00008000) //SCB_CFSR_BFARVALID + { + IRQerrorLog("Fault caused by attempted access to "); + printUnsignedInt(SCB->BFAR); + } else IRQerrorLog("The address that caused the fault is missing\r\n"); + if(cfsr & 0x00001000) //SCB_CFSR_STKERR + IRQerrorLog("Fault occurred during exception stacking\r\n"); + if(cfsr & 0x00000800) //SCB_CFSR_UNSTKERR + IRQerrorLog("Fault occurred during exception unstacking\r\n"); + if(cfsr & 0x00000400) //SCB_CFSR_IMPRECISERR + IRQerrorLog("Fault is imprecise\r\n"); + if(cfsr & 0x00000200) //SCB_CFSR_PRECISERR + IRQerrorLog("Fault is precise\r\n"); + if(cfsr & 0x00000100) //SCB_CFSR_IBUSERR + IRQerrorLog("Fault happened during instruction fetch\r\n"); + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} + +void __attribute__((naked)) UsageFault_Handler() +{ + saveContext(); + //Call UsageFault_impl(). Name is a C++ mangled name. + asm volatile("bl _Z15UsageFault_implv"); + restoreContext(); +} + +void __attribute__((noinline)) UsageFault_impl() +{ + #if defined(WITH_PROCESSES) || defined(WITH_ERRLOG) + unsigned int cfsr=SCB->CFSR; + #endif //WITH_PROCESSES || WITH_ERRLOG + #ifdef WITH_PROCESSES + int id; + if(cfsr & 0x02000000) id=UF_DIVZERO; + else if(cfsr & 0x01000000) id=UF_UNALIGNED; + else if(cfsr & 0x00080000) id=UF_COPROC; + else if(cfsr & 0x00040000) id=UF_EXCRET; + else if(cfsr & 0x00020000) id=UF_EPSR; + else if(cfsr & 0x00010000) id=UF_UNDEF; + else id=UF_UNEXP; + if(miosix::Thread::IRQreportFault(miosix_private::FaultData( + id,getProgramCounter()))) + { + SCB->SHCSR &= ~(1<<12); //Clear USGFAULTPENDED bit + return; + } + #endif //WITH_PROCESSES + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected UsageFault @ "); + printUnsignedInt(getProgramCounter()); + if(cfsr & 0x02000000) //SCB_CFSR_DIVBYZERO + IRQerrorLog("Divide by zero\r\n"); + if(cfsr & 0x01000000) //SCB_CFSR_UNALIGNED + IRQerrorLog("Unaligned memory access\r\n"); + if(cfsr & 0x00080000) //SCB_CFSR_NOCP + IRQerrorLog("Attempted coprocessor access\r\n"); + if(cfsr & 0x00040000) //SCB_CFSR_INVPC + IRQerrorLog("EXC_RETURN not expected now\r\n"); + if(cfsr & 0x00020000) //SCB_CFSR_INVSTATE + IRQerrorLog("Invalid EPSR usage\r\n"); + if(cfsr & 0x00010000) //SCB_CFSR_UNDEFINSTR + IRQerrorLog("Undefined instruction\r\n"); + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} + +void DebugMon_Handler() +{ + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected DebugMon @ "); + printUnsignedInt(getProgramCounter()); + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} + +#endif //_ARCH_CORTEXM0 + +void PendSV_Handler() +{ + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected PendSV @ "); + printUnsignedInt(getProgramCounter()); + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} + +void unexpectedInterrupt() +{ + #ifdef WITH_ERRLOG + IRQerrorLog("\r\n***Unexpected Peripheral interrupt\r\n"); + #endif //WITH_ERRLOG + miosix_private::IRQsystemReboot(); +} diff --git a/lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.h b/lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.h new file mode 100644 index 00000000..2238d32b --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/interrupts_cortexMx.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef INTERRUPTS_H +#define INTERRUPTS_H + +/** + * Called when an unexpected interrupt occurs. + * It is called by stage_1_boot.cpp for all weak interrupts not defined. + */ +void unexpectedInterrupt(); + +/** + * Possible kind of faults that the Cortex-M3 can report. + * They are used to print debug information if a process causes a fault + */ +enum FaultType +{ + MP=1, //Process attempted data access outside its memory + MP_NOADDR=2, //Process attempted data access outside its memory (missing addr) + MP_XN=3, //Process attempted code access outside its memory + UF_DIVZERO=4, //Process attempted to divide by zero + UF_UNALIGNED=5,//Process attempted unaligned memory access + UF_COPROC=6, //Process attempted a coprocessor access + UF_EXCRET=7, //Process attempted an exception return + UF_EPSR=8, //Process attempted to access the EPSR + UF_UNDEF=9, //Process attempted to execute an invalid instruction + UF_UNEXP=10, //Unexpected usage fault + HARDFAULT=11, //Hardfault (for example process executed a BKPT instruction) + BF=12, //Busfault + BF_NOADDR=13 //Busfault (missing addr) +}; + +#endif //INTERRUPTS_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/memory_protection.h b/lib/miosix-kernel/miosix/arch/common/core/memory_protection.h new file mode 100644 index 00000000..5d4fd249 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/memory_protection.h @@ -0,0 +1,13 @@ + +//MPU code is common for all the cortex M cores, so it has been put here + +#ifndef MEMORY_PROTECTION_H +#define MEMORY_PROTECTION_H + +#if defined(_ARCH_CORTEXM3_STM32F2) || defined(_ARCH_CORTEXM4_STM32F4) \ + || defined(_ARCH_CORTEXM7_STM32F7) || defined(_ARCH_CORTEXM7_STM32H7) \ + || defined(_ARCH_CORTEXM3_EFM32GG) || defined(_ARCH_CORTEXM4_STM32L4) +#include "mpu_cortexMx.h" +#endif + +#endif //MEMORY_PROTECTION_H diff --git a/lib/miosix-kernel/miosix/arch/common/core/mpu_cortexMx.cpp b/lib/miosix-kernel/miosix/arch/common/core/mpu_cortexMx.cpp new file mode 100644 index 00000000..77a85db1 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/core/mpu_cortexMx.cpp @@ -0,0 +1,126 @@ +/*************************************************************************** + * Copyright (C) 2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "mpu_cortexMx.h" +#include +#include +#include + +namespace miosix { + +unsigned int sizeToMpu(unsigned int size) +{ + assert(size>=32); + unsigned int result=30-__builtin_clz(size); + if(size & (size-1)) result++; + return result; +} + +#ifdef WITH_PROCESSES + +// +// class MPUConfiguration +// + +MPUConfiguration::MPUConfiguration(unsigned int *elfBase, unsigned int elfSize, + unsigned int *imageBase, unsigned int imageSize) +{ + // NOTE: The ARM documentation is unclear about the effect of the shareable + // bit on a single core architecture. Experimental evidence on an STM32F476 + // shows that setting it in IRQconfigureCache for the internal RAM region + // causes the boot to fail. + // For this reason, all regions are marked as not shareable + regValues[0]=(reinterpret_cast(elfBase) & (~0x1f)) + | MPU_RBAR_VALID_Msk | 6; //Region 6 + regValues[2]=(reinterpret_cast(imageBase) & (~0x1f)) + | MPU_RBAR_VALID_Msk | 7; //Region 7 + regValues[1]=2<>1) & 31)+1)); + char w=regValues[2*i+1] & (1<>1) & 31)+1)); + size_t dataStart=regValues[2] & (~0x1f); + size_t dataEnd=dataStart+(1<<(((regValues[3]>>1) & 31)+1)); + size_t base=reinterpret_cast(ptr); + //The last check is to prevent a wraparound to be considered valid + return ( (base>=codeStart && base+size=dataStart && base+size=base; +} + +bool MPUConfiguration::withinForWriting(const void *ptr, size_t size) const +{ + size_t dataStart=regValues[2] & (~0x1f); + size_t dataEnd=dataStart+(1<<(((regValues[3]>>1) & 31)+1)); + size_t base=reinterpret_cast(ptr); + //The last check is to prevent a wraparound to be considered valid + return base>=dataStart && base+size=base; +} + +bool MPUConfiguration::withinForReading(const char* str) const +{ + size_t codeStart=regValues[0] & (~0x1f); + size_t codeEnd=codeStart+(1<<(((regValues[1]>>1) & 31)+1)); + size_t dataStart=regValues[2] & (~0x1f); + size_t dataEnd=dataStart+(1<<(((regValues[3]>>1) & 31)+1)); + size_t base=reinterpret_cast(str); + if((base>=codeStart) && (base=dataStart) && (base * + ***************************************************************************/ + +#ifndef MPU_CORTEX_MX_H +#define MPU_CORTEX_MX_H + +#include "config/miosix_settings.h" +#include "interfaces/arch_registers.h" +#include + +namespace miosix { + +/** + * \param size in bytes >32 + * \return a value that can be written to MPU->RASR to represent that size + */ +unsigned int sizeToMpu(unsigned int size); + +/** + * To be called at boot to enable the MPU. + * Without calling this function, the MPU will not work even if regions are + * configured in MPUConfiguration + */ +inline void IRQenableMPUatBoot() +{ + MPU->CTRL=MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk; +} + +#ifdef WITH_PROCESSES + +/** + * \internal + * This class is used to manage the MemoryProtectionUnit + */ +class MPUConfiguration +{ +public: + /** + * Default constructor, leaves the MPU regions unconfigured + */ + MPUConfiguration() {} + + /** + * \internal + * \param elfBase base address of the ELF file + * \param elfSize size of the ELF file + * \param imageBase base address of the Process RAM image + * \param imageSize size of the Process RAM image + */ + MPUConfiguration(unsigned int *elfBase, unsigned int elfSize, + unsigned int *imageBase, unsigned int imageSize); + + /** + * \internal + * This method is used to configure the Memoy Protection region for a + * Process during a context-switch to a userspace thread. + * Can only be called inside an IRQ, not even with interrupts disabled + */ + void IRQenable() + { + MPU->RBAR=regValues[0]; + MPU->RASR=regValues[1]; + MPU->RBAR=regValues[2]; + MPU->RASR=regValues[3]; + __set_CONTROL(3); + } + + /** + * \internal + * This method is used to disable the MPU during a context-switch to a + * kernelspace thread. + * Can only be called inside an IRQ, not even with interrupts disabled + */ + static void IRQdisable() + { + __set_CONTROL(2); + } + + /** + * Print the MPU configuration for debugging purposes + */ + void dumpConfiguration(); + + /** + * Some MPU implementations may not allow regions of arbitrary size, + * this function allows to round a size up to the minimum value that + * the MPU support. + * \param size the size of a memory area to be configured as an MPU + * region + * \return the size rounded to the minimum MPU region allowed that is + * greater or equal to the given size + */ + static unsigned int roundSizeForMPU(unsigned int size); + + /** + * Check if a buffer is within a readable segment of the process + * \param ptr base pointer of the buffer to check + * \param size buffer size + * \return true if the buffer is correctly within the process + */ + bool withinForReading(const void *ptr, size_t size) const; + + /** + * Check if a buffer is within a writable segment of the process + * \param ptr base pointer of the buffer to check + * \param size buffer size + * \return true if the buffer is correctly within the process + */ + bool withinForWriting(const void *ptr, size_t size) const; + + /** + * Check if a nul terminated string is entirely contained in the process, + * \param str a pointer to a nul terminated string + * \return true if the buffer is correctly within the process + */ + bool withinForReading(const char *str) const; + + //Uses default copy constructor and operator= +private: + ///These value are copied into the MPU registers to configure them + unsigned int regValues[4]; +}; + +#endif //WITH_PROCESSES + +} //namespace miosix + +#endif //MPU_CORTEX_MX_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/dcc.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/dcc.cpp new file mode 100644 index 00000000..c4d93752 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/dcc.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (C) 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +/* + * DCC support inspired by dcc_stdio.c in OpenOCD + */ + +#include +#include +#include "filesystem/ioctl.h" +#include "dcc.h" + +namespace miosix { + +ssize_t ARMDCC::readBlock(void* buffer, size_t size, off_t where) +{ + return -EBADF; +} + +ssize_t ARMDCC::writeBlock(const void* buffer, size_t size, off_t where) +{ + Lock l(mutex); + debugStr(reinterpret_cast(buffer),size); + return size; +} + +void ARMDCC::IRQwrite(const char* str) +{ + //Actually, there is a race condition if IRQdebugWrite is called from an + //interrupt while the main code is printing with debugWrite, but it is + //not such an issue since IRQdebugWrite is called only + //- at boot before the kernel is started, so there are no other threads + //- in case of a serious error, to print what went wrong before rebooting + //In the second case, which is rare, the data may not be printed correctly + debugStr(str,-1); +} + +int ARMDCC::ioctl(int cmd, void* arg) +{ + if(cmd==IOCTL_SYNC) return 0; //Nothing to do, but say we did somaething + return -ENOTTY; //Means the operation does not apply to this descriptor +} + +void ARMDCC::send(unsigned char c) +{ + const unsigned int busy=1; + while(CoreDebug->DCRDR & busy) ; + CoreDebug->DCRDR=(static_cast(c)<<8) | busy; +} + +void ARMDCC::debugStr(const char *str, int length) +{ + //If not being debugged, don't print anything + if((CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)==0) return; + + if(length<0) length=strlen(str); + if(length>0 && str[0]=='\r') str++, length--; //TODO: better \r\n removal + if(length>0 && str[0]=='\n') str++, length--; + if(length==0) return; + send(1); //1=sending a string + send(0); + send(length & 0xff); + send(length>>8); + for(int i=0;i * + ***************************************************************************/ + +#ifndef DCC_H +#define DCC_H + +#include "filesystem/console/console_device.h" +#include "kernel/sync.h" + +namespace miosix { + +/** + * This class exposes the ARM debug communication channel through the Device + * interface, to allow redirecting stdin and stdout to the DCC for boards that + * have JTAG/SWD but no serial port + */ +class ARMDCC : public Device +{ +public: + /** + * Constructor. + */ + ARMDCC() : Device(Device::TTY) {} + + /** + * Read a block of data. As the DCC is write only, this function returns an + * error + * \param buffer buffer where read data will be stored + * \param size buffer size + * \param where where to read from + * \return number of bytes read or a negative number on failure + */ + ssize_t readBlock(void *buffer, size_t size, off_t where); + + /** + * Write a block of data + * \param buffer buffer where take data to write + * \param size buffer size + * \param where where to write to + * \return number of bytes written or a negative number on failure + */ + ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + /** + * Write a string. + * An extension to the Device interface that adds a new member function, + * which is used by the kernel on console devices to write debug information + * before the kernel is started or in case of serious errors, right before + * rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * \param str the string to write. The string must be NUL terminated. + */ + void IRQwrite(const char *str); + + /** + * Performs device-specific operations + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int ioctl(int cmd, void *arg); + +private: + /** + * Send data to the host + */ + static void send(unsigned char c); + + /** + * Send a line of text to the host. + * OpenOCD will insert a \n after each line, unfortunately, + * as this complicates things. + */ + static void debugStr(const char *str, int length); + + FastMutex mutex; +}; + +} //namespace miosix + +#endif //DCC_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/sd_lpc2000.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/sd_lpc2000.cpp new file mode 100644 index 00000000..4ff56e68 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/sd_lpc2000.cpp @@ -0,0 +1,587 @@ +/* + * Integration in Miosix by Terraneo Federico. + * Based on code by Martin Thomas to initialize SD cards from LPC2000 + */ + +#include "sd_lpc2000.h" +#include "interfaces/bsp.h" +#include "LPC213x.h" +#include "board_settings.h" //For sdVoltage +#include +#include + +//Note: enabling debugging might cause deadlock when using sleep() or reboot() +//The bug won't be fixed because debugging is only useful for driver development +///\internal Debug macro, for normal conditions +//#define DBG iprintf +#define DBG(x,...) do {} while(0) +///\internal Debug macro, for errors only +//#define DBGERR iprintf +#define DBGERR(x,...) do {} while(0) + +namespace miosix { + +///\internal Type of card (1<<0)=MMC (1<<1)=SDv1 (1<<2)=SDv2 (1<<2)|(1<<3)=SDHC +static unsigned char cardType=0; + +/* + * Definitions for MMC/SDC command. + * A command has the following format: + * - 1 bit @ 0 (start bit) + * - 1 bit @ 1 (transmission bit) + * - 6 bit which identify command index (CMD0..CMD63) + * - 32 bit command argument + * - 7 bit CRC + * - 1 bit @ 1 (end bit) + * In addition, ACMDxx are the sequence of two commands, CMD55 and CMDxx + * These constants have the following meaninig: + * - bit #7 @ 1 indicates that it is an ACMD. send_cmd() will send CMD55, then + * clear this bit and send the command with this bit @ 0 (which is start bit) + * - bit #6 always @ 1, because it is the transmission bit + * - remaining 6 bit represent command index + */ +#define CMD0 (0x40+0) /* GO_IDLE_STATE */ +#define CMD1 (0x40+1) /* SEND_OP_COND (MMC) */ +#define ACMD41 (0xC0+41) /* SEND_OP_COND (SDC) */ +#define CMD8 (0x40+8) /* SEND_IF_COND */ +#define CMD9 (0x40+9) /* SEND_CSD */ +#define CMD10 (0x40+10) /* SEND_CID */ +#define CMD12 (0x40+12) /* STOP_TRANSMISSION */ +#define CMD13 (0x40+13) /* SEND_STATUS */ +#define ACMD13 (0xC0+13) /* SD_STATUS (SDC) */ +#define CMD16 (0x40+16) /* SET_BLOCKLEN */ +#define CMD17 (0x40+17) /* READ_SINGLE_BLOCK */ +#define CMD18 (0x40+18) /* READ_MULTIPLE_BLOCK */ +#define CMD23 (0x40+23) /* SET_BLOCK_COUNT (MMC) */ +#define ACMD23 (0xC0+23) /* SET_WR_BLK_ERASE_COUNT (SDC) */ +#define CMD24 (0x40+24) /* WRITE_BLOCK */ +#define CMD25 (0x40+25) /* WRITE_MULTIPLE_BLOCK */ +#define CMD42 (0x40+42) /* LOCK_UNLOCK */ +#define CMD55 (0x40+55) /* APP_CMD */ +#define CMD58 (0x40+58) /* READ_OCR */ + +// SSPCR0 Bit-Definitions +#define CPOL 6 +#define CPHA 7 +// SSPCR1 Bit-Defintions +#define SSE 1 +#define MS 2 +#define SCR 8 +// SSPSR Bit-Definitions +#define TNF 1 +#define RNE 2 +#define BSY 4 + +#define SPI_SCK_PIN 17 // SCK P0.17 out +#define SPI_MISO_PIN 18 // MISO P0.18 in +#define SPI_MOSI_PIN 19 // MOSI P0.19 out +#define SPI_SS_PIN 20 // CS p0.20 out + +#define SPI_SCK_FUNCBIT 2 +#define SPI_MISO_FUNCBIT 4 +#define SPI_MOSI_FUNCBIT 6 +#define SPI_SS_FUNCBIT 8 + +///\internal Maximum speed 14745600/2=7372800 +#define SPI_PRESCALE_MIN 2 + +///\internal Select/unselect card +#define CS_LOW() IOCLR0 = (1<0x00FF) + print_error_code((unsigned char)(value>>8)); + else + DBGERR("Unknown error 0x%x\n",value); + break; + } + return -1; +} + +/** + * \internal + * Wait until card is ready + */ +static unsigned char wait_ready() +{ + unsigned char result; + spi_1_send(0xff); + for(int i=0;i<460800;i++)//Timeout ~500ms + { + result=spi_1_send(0xff); + if(result==0xff) return 0xff; + if(i%500==0) DBG("*\n"); + } + DBGERR("Error: wait_ready()\n"); + return result; +} + +/** + * \internal + * Send a command to the SD card + * \param cmd one among the #define'd commands + * \param arg command's argument + * \return command's r1 response. If command returns a longer response, the user + * can continue reading the response with spi_1_send(0xff) + */ +static unsigned char send_cmd(unsigned char cmd, unsigned int arg) +{ + unsigned char n, res; + if(cmd & 0x80) + { // ACMD is the command sequence of CMD55-CMD + cmd&=0x7f; + res=send_cmd(CMD55,0); + if(res>1) return res; + } + + // Select the card and wait for ready + CS_HIGH(); + CS_LOW(); + + if(cmd==CMD0) + { + //wait_ready on CMD0 seems to cause infinite loop + spi_1_send(0xff); + } else { + if(wait_ready()!=0xff) return 0xff; + } + // Send command + spi_1_send(cmd); // Start + Command index + spi_1_send((unsigned char)(arg >> 24)); // Argument[31..24] + spi_1_send((unsigned char)(arg >> 16)); // Argument[23..16] + spi_1_send((unsigned char)(arg >> 8)); // Argument[15..8] + spi_1_send((unsigned char)arg); // Argument[7..0] + n=0x01; // Dummy CRC + Stop + if (cmd==CMD0) n=0x95; // Valid CRC for CMD0(0) + if (cmd==CMD8) n=0x87; // Valid CRC for CMD8(0x1AA) + spi_1_send(n); + // Receive response + if (cmd==CMD12) spi_1_send(0xff); // Skip a stuff byte when stop reading + n=10; // Wait response, try 10 times + do + res=spi_1_send(0xff); + while ((res & 0x80) && --n); + return res; // Return with the response value +} + +/** + * \internal + * Receive a data packet from the SD card + * \param buf data buffer to store received data + * \param byte count (must be multiple of 4) + * \return true on success, false on failure + */ +static bool rx_datablock(unsigned char *buf, unsigned int btr) +{ + unsigned char token; + for(int i=0;i<0xffff;i++) + { + token=spi_1_send(0xff); + if(token!=0xff) break; + } + if(token!=0xfe) return false; // If not valid data token, retutn error + + do { // Receive the data block into buffer + *buf=spi_1_send(0xff); buf++; + *buf=spi_1_send(0xff); buf++; + *buf=spi_1_send(0xff); buf++; + *buf=spi_1_send(0xff); buf++; + } while(btr-=4); + spi_1_send(0xff); // Discard CRC + spi_1_send(0xff); + + return true; // Return success +} + +/** + * \internal + * Send a data packet to the SD card + * \param buf 512 byte data block to be transmitted + * \param token data start/stop token + * \return true on success, false on failure + */ +static bool tx_datablock (const unsigned char *buf, unsigned char token) +{ + unsigned char resp; + if(wait_ready()!=0xff) return false; + + spi_1_send(token); // Xmit data token + if (token!=0xfd) + { // Is data token + for(int i=0;i<256;i++) + { // Xmit the 512 byte data block + spi_1_send(*buf); buf++; + spi_1_send(*buf); buf++; + } + spi_1_send(0xff); // CRC (Dummy) + spi_1_send(0xff); + resp=spi_1_send(0xff); // Receive data response + if((resp & 0x1f)!=0x05) // If not accepted, return error + return false; + } + return true; +} + +// +// class SPISDDriver +// + +intrusive_ref_ptr SPISDDriver::instance() +{ + static FastMutex m; + static intrusive_ref_ptr instance; + Lock l(m); + if(!instance) instance=new SPISDDriver(); + return instance; +} + +ssize_t SPISDDriver::readBlock(void* buffer, size_t size, off_t where) +{ + if(where % 512 || size % 512) return -EFAULT; + unsigned int lba=where/512; + unsigned int nSectors=size/512; + unsigned char *buf=reinterpret_cast(buffer); + Lock l(mutex); + DBG("SPISDDriver::readBlock(): nSectors=%d\n",nSectors); + if(!(cardType & 8)) lba*=512; // Convert to byte address if needed + unsigned char result; + if(nSectors==1) + { // Single block read + result=send_cmd(CMD17,lba); // READ_SINGLE_BLOCK + if(result!=0) + { + print_error_code(result); + CS_HIGH(); + return -EBADF; + } + if(rx_datablock(buf,512)==false) + { + DBGERR("Block read error\n"); + CS_HIGH(); + return -EBADF; + } + } else { // Multiple block read + //DBGERR("Mbr\n");//debug only + result=send_cmd(CMD18,lba); // READ_MULTIPLE_BLOCK + if(result!=0) + { + print_error_code(result); + CS_HIGH(); + return -EBADF; + } + do { + if(!rx_datablock(buf,512)) break; + buf+=512; + } while(--nSectors); + send_cmd(CMD12,0); // STOP_TRANSMISSION + if(nSectors!=0) + { + DBGERR("Multiple block read error\n"); + CS_HIGH(); + return -EBADF; + } + } + CS_HIGH(); + return size; +} + +ssize_t SPISDDriver::writeBlock(const void* buffer, size_t size, off_t where) +{ + if(where % 512 || size % 512) return -EFAULT; + unsigned int lba=where/512; + unsigned int nSectors=size/512; + const unsigned char *buf=reinterpret_cast(buffer); + Lock l(mutex); + DBG("SPISDDriver::writeBlock(): nSectors=%d\n",nSectors); + if(!(cardType & 8)) lba*=512; // Convert to byte address if needed + unsigned char result; + if(nSectors==1) + { // Single block write + result=send_cmd(CMD24,lba); // WRITE_BLOCK + if(result!=0) + { + print_error_code(result); + CS_HIGH(); + return -EBADF; + } + if(tx_datablock(buf,0xfe)==false) // WRITE_BLOCK + { + DBGERR("Block write error\n"); + CS_HIGH(); + return -EBADF; + } + } else { // Multiple block write + //DBGERR("Mbw\n");//debug only + if(cardType & 6) send_cmd(ACMD23,nSectors);//Only if it is SD card + result=send_cmd(CMD25,lba); // WRITE_MULTIPLE_BLOCK + if(result!=0) + { + print_error_code(result); + CS_HIGH(); + return -EBADF; + } + do { + if(!tx_datablock(buf,0xfc)) break; + buf+=512; + } while(--nSectors); + if(!tx_datablock(0,0xfd)) // STOP_TRAN token + { + DBGERR("Multiple block write error\n"); + CS_HIGH(); + return -EBADF; + } + } + CS_HIGH(); + return size; +} + +int SPISDDriver::ioctl(int cmd, void* arg) +{ + DBG("SPISDDriver::ioctl()\n"); + if(cmd!=IOCTL_SYNC) return -ENOTTY; + Lock l(mutex); + CS_LOW(); + unsigned char result=wait_ready(); + CS_HIGH(); + if(result==0xff) return 0; + else return -EFAULT; +} + +SPISDDriver::SPISDDriver() : Device(Device::BLOCK) +{ + const int MAX_RETRY=20;//Maximum command retry before failing + spi_1_init(); /* init at low speed */ + unsigned char resp; + int i; + for(i=0;i * + ***************************************************************************/ + +#ifndef SD_LPC2000_H +#define SD_LPC2000_H + +#include "kernel/sync.h" +#include "filesystem/devfs/devfs.h" +#include "filesystem/ioctl.h" + +namespace miosix { + +/** + * Driver for interfacing to an SD card through SPI + */ +class SPISDDriver : public Device +{ +public: + /** + * \return an instance to this class, singleton + */ + static intrusive_ref_ptr instance(); + + virtual ssize_t readBlock(void *buffer, size_t size, off_t where); + + virtual ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + virtual int ioctl(int cmd, void *arg); +private: + /** + * Constructor + */ + SPISDDriver(); + + FastMutex mutex; +}; + +} //namespace miosix + +#endif //SD_LPC2000_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.cpp new file mode 100644 index 00000000..13a5ee23 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.cpp @@ -0,0 +1,1776 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "sd_stm32f1.h" +#include "interfaces/bsp.h" +#include "interfaces/arch_registers.h" +#include "interfaces/delays.h" +#include "kernel/kernel.h" +#include "kernel/scheduler/scheduler.h" +#include "board_settings.h" //For sdVoltage +#include +#include +#include + +/* + * This driver is quite a bit complicated, due to a silicon errata in the + * STM32F1 microcontrollers, that prevents concurrent access to the FSMC + * (i.e., the external memory controller) by both the CPU and DMA. + * Therefore, if __ENABLE_XRAM is defined, the SDIO peripheral is used in + * polled mode, otherwise in DMA mode. The use in polled mode is further + * complicated by the fact that the SDIO peripheral does not halt the clock + * to the SD card if its internal fifo is full. Therefore, when using the + * SDIO in polled mode the only solution is to disable interrupts during + * the data transfer. To optimize reading and writing speed this code + * automatically chooses the best transfer speed using a binary search during + * card initialization. Also, other sources of mess are the requirement for + * word alignment of pointers when doing DMA transfers or writing to the SDIO + * peripheral. Because of that, tryng to fwrite() large bloks of data is faster + * if they are word aligned. An easy way to do so is to allocate them on the + * heap (and not doing any pointer arithmetic on the value returned by + * malloc/new) + */ + +//Note: enabling debugging might cause deadlock when using sleep() or reboot() +//The bug won't be fixed because debugging is only useful for driver development +///\internal Debug macro, for normal conditions +//#define DBG iprintf +#define DBG(x,...) do {} while(0) +///\internal Debug macro, for errors only +//#define DBGERR iprintf +#define DBGERR(x,...) do {} while(0) + +#ifndef __ENABLE_XRAM +/** + * \internal + * DMA2 Channel4 interrupt handler + */ +void __attribute__((naked)) DMA2_Channel4_5_IRQHandler() +{ + saveContext(); + asm volatile("bl _ZN6miosix19DMA2channel4irqImplEv"); + restoreContext(); +} + +/** + * \internal + * SDIO interrupt handler + */ +void __attribute__((naked)) SDIO_IRQHandler() +{ + saveContext(); + asm volatile("bl _ZN6miosix11SDIOirqImplEv"); + restoreContext(); +} +#endif //__ENABLE_XRAM + +namespace miosix { + +#ifndef __ENABLE_XRAM +static volatile bool transferError; ///< \internal DMA or SDIO transfer error +static Thread *waiting; ///< \internal Thread waiting for transfer +static unsigned int dmaFlags; ///< \internal DMA status flags +static unsigned int sdioFlags; ///< \internal SDIO status flags + +/** + * \internal + * DMA2 Channel4 interrupt handler actual implementation + */ +void __attribute__((used)) DMA2channel4irqImpl() +{ + dmaFlags=DMA2->ISR; + if(dmaFlags & DMA_ISR_TEIF4) transferError=true; + + DMA2->IFCR=DMA_IFCR_CGIF4; + + if(!waiting) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * \internal + * DMA2 Channel4 interrupt handler actual implementation + */ +void __attribute__((used)) SDIOirqImpl() +{ + sdioFlags=SDIO->STA; + if(sdioFlags & (SDIO_STA_STBITERR | SDIO_STA_RXOVERR | + SDIO_STA_TXUNDERR | SDIO_STA_DTIMEOUT | SDIO_STA_DCRCFAIL)) + transferError=true; + + SDIO->ICR=0x7ff;//Clear flags + + if(!waiting) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} +#endif //__ENABLE_XRAM + +/* + * Operating voltage of device. It is sent to the SD card to check if it can + * work at this voltage. Range *must* be within 28..36 + * Example 33=3.3v + */ +//const unsigned char sdVoltage=33; //Is defined in board_settings.h +const unsigned int sdVoltageMask=1<<(sdVoltage-13); //See OCR register in SD spec + +/** + * \internal + * Possible state of the cardType variable. + */ +enum CardType +{ + Invalid=0, ///<\internal Invalid card type + MMC=1<<0, ///<\internal if(cardType==MMC) card is an MMC + SDv1=1<<1, ///<\internal if(cardType==SDv1) card is an SDv1 + SDv2=1<<2, ///<\internal if(cardType==SDv2) card is an SDv2 + SDHC=1<<3 ///<\internal if(cardType==SDHC) card is an SDHC +}; + +///\internal Type of card. +static CardType cardType=Invalid; + +//SD card GPIOs +typedef Gpio sdD0; +typedef Gpio sdD1; +typedef Gpio sdD2; +typedef Gpio sdD3; +typedef Gpio sdCLK; +typedef Gpio sdCMD; + +// +// Class BufferConverter +// + +/** + * \internal + * Convert a single buffer of *fixed* and predetermined size to and from + * word-aligned. To do so, if the buffer is already word aligned a cast is made, + * otherwise a new buffer is allocated. + * Note that this class allocates at most ONE buffer at any given time. + * Therefore any call to toWordAligned(), toWordAlignedWithoutCopy(), + * toOriginalBuffer() or deallocateBuffer() invalidates the buffer previousy + * returned by toWordAligned() and toWordAlignedWithoutCopy() + */ +class BufferConverter +{ +public: + /** + * \internal + * The buffer will be of this size only. + */ + static const int BUFFER_SIZE=512; + + /** + * \internal + * \return true if the pointer is word aligned + */ + static bool isWordAligned(const void *x) + { + return (reinterpret_cast(x) & 0x3)==0; + } + + /** + * \internal + * Convert from a constunsigned char* buffer of size BUFFER_SIZE to a + * const unsigned int* word aligned buffer. + * If the original buffer is already word aligned it only does a cast, + * otherwise it copies the data on the original buffer to a word aligned + * buffer. Useful if subseqent code will read from the buffer. + * \param a buffer of size BUFFER_SIZE. Can be word aligned or not. + * \return a word aligned buffer with the same data of the given buffer + */ + static const unsigned int *toWordAligned(const unsigned char *buffer); + + /** + * \internal + * Convert from an unsigned char* buffer of size BUFFER_SIZE to an + * unsigned int* word aligned buffer. + * If the original buffer is already word aligned it only does a cast, + * otherwise it returns a new buffer which *does not* contain the data + * on the original buffer. Useful if subseqent code will write to the + * buffer. To move the written data to the original buffer, use + * toOriginalBuffer() + * \param a buffer of size BUFFER_SIZE. Can be word aligned or not. + * \return a word aligned buffer with undefined content. + */ + static unsigned int *toWordAlignedWithoutCopy(unsigned char *buffer); + + /** + * \internal + * Convert the buffer got through toWordAlignedWithoutCopy() to the + * original buffer. If the original buffer was word aligned, nothing + * happens, otherwise a memcpy is done. + * Note that this function does not work on buffers got through + * toWordAligned(). + */ + static void toOriginalBuffer(); + + /** + * \internal + * Can be called to deallocate the buffer + */ + static void deallocateBuffer(); + +private: + static unsigned char *originalBuffer; + static unsigned int *wordAlignedBuffer; +}; + +const unsigned int *BufferConverter::toWordAligned(const unsigned char *buffer) +{ + originalBuffer=0; //Tell toOriginalBuffer() that there's nothing to do + if(isWordAligned(buffer)) + { + return reinterpret_cast(buffer); + } else { + if(wordAlignedBuffer==0) + wordAlignedBuffer=new unsigned int[BUFFER_SIZE/sizeof(unsigned int)]; + std::memcpy(wordAlignedBuffer,buffer,BUFFER_SIZE); + return wordAlignedBuffer; + } +} + +unsigned int *BufferConverter::toWordAlignedWithoutCopy( + unsigned char *buffer) +{ + if(isWordAligned(buffer)) + { + originalBuffer=0; //Tell toOriginalBuffer() that there's nothing to do + return reinterpret_cast(buffer); + } else { + originalBuffer=buffer; //Save original pointer for toOriginalBuffer() + if(wordAlignedBuffer==0) + wordAlignedBuffer=new unsigned int[BUFFER_SIZE/sizeof(unsigned int)]; + return wordAlignedBuffer; + } +} + +void BufferConverter::toOriginalBuffer() +{ + if(originalBuffer==0) return; + std::memcpy(originalBuffer,wordAlignedBuffer,BUFFER_SIZE); + originalBuffer=0; +} + +void BufferConverter::deallocateBuffer() +{ + originalBuffer=0; //Invalidate also original buffer + if(wordAlignedBuffer!=0) + { + delete[] wordAlignedBuffer; + wordAlignedBuffer=0; + } +} + +unsigned char *BufferConverter::originalBuffer=0; +unsigned int *BufferConverter::wordAlignedBuffer=0; + +// +// Class CmdResult +// + +/** + * \internal + * Contains the result of an SD/MMC command + */ +class CmdResult +{ +public: + + /** + * \internal + * Possible outcomes of sending a command + */ + enum Error + { + Ok=0, /// No errors + Timeout, /// Timeout while waiting command reply + CRCFail, /// CRC check failed in command reply + RespNotMatch,/// Response index does not match command index + ACMDFail /// Sending CMD55 failed + }; + + /** + * \internal + * Default constructor + */ + CmdResult(): cmd(0), error(Ok), response(0) {} + + /** + * \internal + * Constructor, set the response data + * \param cmd command index of command that was sent + * \param result result of command + */ + CmdResult(unsigned char cmd, Error error): cmd(cmd), error(error), + response(SDIO->RESP1) {} + + /** + * \internal + * \return the 32 bit of the response. + * May not be valid if getError()!=Ok or the command does not send a + * response, such as CMD0 + */ + unsigned int getResponse() { return response; } + + /** + * \internal + * \return command index + */ + unsigned char getCmdIndex() { return cmd; } + + /** + * \internal + * \return the error flags of the response + */ + Error getError() { return error; } + + /** + * \internal + * Checks if errors occurred while sending the command. + * \return true if no errors, false otherwise + */ + bool validateError(); + + /** + * \internal + * interprets this->getResponse() as an R1 response, and checks if there are + * errors, or everything is ok + * \return true on success, false on failure + */ + bool validateR1Response(); + + /** + * \internal + * Same as validateR1Response, but can be called with interrupts disabled. + * \return true on success, false on failure + */ + bool IRQvalidateR1Response(); + + /** + * \internal + * interprets this->getResponse() as an R6 response, and checks if there are + * errors, or everything is ok + * \return true on success, false on failure + */ + bool validateR6Response(); + + /** + * \internal + * \return the card state from an R1 or R6 resonse + */ + unsigned char getState(); + +private: + unsigned char cmd; ///<\internal Command index that was sent + Error error; ///<\internal possible error that occurred + unsigned int response; ///<\internal 32bit response +}; + +bool CmdResult::validateError() +{ + switch(error) + { + case Ok: + return true; + case Timeout: + DBGERR("CMD%d: Timeout\n",cmd); + break; + case CRCFail: + DBGERR("CMD%d: CRC Fail\n",cmd); + break; + case RespNotMatch: + DBGERR("CMD%d: Response does not match\n",cmd); + break; + case ACMDFail: + DBGERR("CMD%d: ACMD Fail\n",cmd); + break; + } + return false; +} + +bool CmdResult::validateR1Response() +{ + if(error!=Ok) return validateError(); + //Note: this number is obtained with all the flags of R1 which are errors + //(flagged as E in the SD specification), plus CARD_IS_LOCKED because + //locked card are not supported by this software driver + if((response & 0xfff98008)==0) return true; + DBGERR("CMD%d: R1 response error(s):\n",cmd); + if(response & (1<<31)) DBGERR("Out of range\n"); + if(response & (1<<30)) DBGERR("ADDR error\n"); + if(response & (1<<29)) DBGERR("BLOCKLEN error\n"); + if(response & (1<<28)) DBGERR("ERASE SEQ error\n"); + if(response & (1<<27)) DBGERR("ERASE param\n"); + if(response & (1<<26)) DBGERR("WP violation\n"); + if(response & (1<<25)) DBGERR("card locked\n"); + if(response & (1<<24)) DBGERR("LOCK_UNLOCK failed\n"); + if(response & (1<<23)) DBGERR("command CRC failed\n"); + if(response & (1<<22)) DBGERR("illegal command\n"); + if(response & (1<<21)) DBGERR("ECC fail\n"); + if(response & (1<<20)) DBGERR("card controller error\n"); + if(response & (1<<19)) DBGERR("unknown error\n"); + if(response & (1<<16)) DBGERR("CSD overwrite\n"); + if(response & (1<<15)) DBGERR("WP ERASE skip\n"); + if(response & (1<<3)) DBGERR("AKE_SEQ error\n"); + return false; +} + +bool CmdResult::IRQvalidateR1Response() +{ + if(error!=Ok) return false; + if(response & 0xfff98008) return false; + return true; +} + +bool CmdResult::validateR6Response() +{ + if(error!=Ok) return validateError(); + if((response & 0xe008)==0) return true; + DBGERR("CMD%d: R6 response error(s):\n",cmd); + if(response & (1<<15)) DBGERR("command CRC failed\n"); + if(response & (1<<14)) DBGERR("illegal command\n"); + if(response & (1<<13)) DBGERR("unknown error\n"); + if(response & (1<<3)) DBGERR("AKE_SEQ error\n"); + return false; +} + +unsigned char CmdResult::getState() +{ + unsigned char result=(response>>9) & 0xf; + DBG("CMD%d: State: ",cmd); + switch(result) + { + case 0: DBG("Idle\n"); break; + case 1: DBG("Ready\n"); break; + case 2: DBG("Ident\n"); break; + case 3: DBG("Stby\n"); break; + case 4: DBG("Tran\n"); break; + case 5: DBG("Data\n"); break; + case 6: DBG("Rcv\n"); break; + case 7: DBG("Prg\n"); break; + case 8: DBG("Dis\n"); break; + case 9: DBG("Btst\n"); break; + default: DBG("Unknown\n"); break; + } + return result; +} + +// +// Class Command +// + +/** + * \internal + * This class allows sending commands to an SD or MMC + */ +class Command +{ +public: + + /** + * \internal + * SD/MMC commands + * - bit #7 is @ 1 if a command is an ACMDxx. send() will send the + * sequence CMD55, CMDxx + * - bit from #0 to #5 indicate command index (CMD0..CMD63) + * - bit #6 is don't care + */ + enum CommandType + { + CMD0=0, //GO_IDLE_STATE + CMD2=2, //ALL_SEND_CID + CMD3=3, //SEND_RELATIVE_ADDR + ACMD6=0x80 | 6, //SET_BUS_WIDTH + CMD7=7, //SELECT_DESELECT_CARD + ACMD41=0x80 | 41, //SEND_OP_COND (SD) + CMD8=8, //SEND_IF_COND + CMD9=9, //SEND_CSD + CMD12=12, //STOP_TRANSMISSION + CMD13=13, //SEND_STATUS + CMD16=16, //SET_BLOCKLEN + CMD17=17, //READ_SINGLE_BLOCK + CMD18=18, //READ_MULTIPLE_BLOCK + ACMD23=0x80 | 23, //SET_WR_BLK_ERASE_COUNT (SD) + CMD24=24, //WRITE_BLOCK + CMD25=25, //WRITE_MULTIPLE_BLOCK + CMD55=55 //APP_CMD + }; + + /** + * \internal + * Send a command. + * \param cmd command index (CMD0..CMD63) or ACMDxx command + * \param arg the 32 bit argument to the command + * \return a CmdResult object + */ + static CmdResult send(CommandType cmd, unsigned int arg) + { + if(static_cast(cmd) & 0x80) + { + DBG("ACMD%d\n",static_cast(cmd) & 0x3f); + } else { + DBG("CMD%d\n",static_cast(cmd) & 0x3f); + } + return IRQsend(cmd,arg); + } + + /** + * \internal + * Send a command. Can be called with interrupts disabled as it does not + * print any debug information. + * \param cmd command index (CMD0..CMD63) or ACMDxx command + * \param arg the 32 bit argument to the command + * \return a CmdResult object + */ + static CmdResult IRQsend(CommandType cmd, unsigned int arg); + + /** + * \internal + * Set the relative card address, obtained during initialization. + * \param r the card's rca + */ + static void setRca(unsigned short r) { rca=r; } + + /** + * \internal + * \return the card's rca, as set by setRca + */ + static unsigned int getRca() { return static_cast(rca); } + +private: + static unsigned short rca;///<\internal Card's relative address +}; + +CmdResult Command::IRQsend(CommandType cmd, unsigned int arg) +{ + unsigned char cc=static_cast(cmd); + //Handle ACMDxx as CMD55, CMDxx + if(cc & 0x80) + { + CmdResult r=IRQsend(CMD55,(static_cast(rca))<<16); + if(r.IRQvalidateR1Response()==false) + return CmdResult(cc & 0x3f,CmdResult::ACMDFail); + //Bit 5 @ 1 = next command will be interpreted as ACMD + if((r.getResponse() & (1<<5))==0) + return CmdResult(cc & 0x3f,CmdResult::ACMDFail); + } + + //Send command + cc &= 0x3f; + unsigned int command=SDIO_CMD_CPSMEN | static_cast(cc); + if(cc!=CMD0) command |= SDIO_CMD_WAITRESP_0; //CMD0 has no response + if(cc==CMD2) command |= SDIO_CMD_WAITRESP_1; //CMD2 has long response + if(cc==CMD9) command |= SDIO_CMD_WAITRESP_1; //CMD9 has long response + SDIO->ARG=arg; + SDIO->CMD=command; + + //CMD0 has no response, so wait until it is sent + if(cc==CMD0) + { + for(int i=0;i<500;i++) + { + if(SDIO->STA & SDIO_STA_CMDSENT) + { + SDIO->ICR=0x7ff;//Clear flags + return CmdResult(cc,CmdResult::Ok); + } + delayUs(1); + } + SDIO->ICR=0x7ff;//Clear flags + return CmdResult(cc,CmdResult::Timeout); + } + + //Command is not CMD0, so wait a reply + for(int i=0;i<500;i++) + { + unsigned int status=SDIO->STA; + if(status & SDIO_STA_CMDREND) + { + SDIO->ICR=0x7ff;//Clear flags + if(SDIO->RESPCMD==cc) return CmdResult(cc,CmdResult::Ok); + else return CmdResult(cc,CmdResult::RespNotMatch); + } + if(status & SDIO_STA_CCRCFAIL) + { + SDIO->ICR=SDIO_ICR_CCRCFAILC; + return CmdResult(cc,CmdResult::CRCFail); + } + if(status & SDIO_STA_CTIMEOUT) break; + delayUs(1); + } + SDIO->ICR=SDIO_ICR_CTIMEOUTC; + return CmdResult(cc,CmdResult::Timeout); +} + +unsigned short Command::rca=0; + +// +// Class DataResult +// + +/** + * \internal + * Contains the result of sending/receiving a data block + */ +class DataResult +{ +public: + + /** + * \internal + * Possible outcomes of sending or receiving data + */ + enum Error + { + Ok=0, + Timeout, + CRCFail, + RXOverrun, + TXUnderrun, + StartBitFail + }; + + /** + * \internal + * Default constructor + */ + DataResult(): error(Ok) {} + + /** + * \internal + * Constructor, set the result. + * \param error error type + */ + DataResult(Error error): error(error) {} + + /** + * \internal + * \return the error flags + */ + Error getError() { return error; } + + /** + * \internal + * Checks if errors occurred while sending/receiving data. + * \return true if no errors, false otherwise + */ + bool validateError(); + +private: + Error error; +}; + + +bool DataResult::validateError() +{ + switch(error) + { + case Ok: + return true; + case Timeout: + DBGERR("Data Timeout\n"); + break; + case CRCFail: + DBGERR("Data CRC Fail\n"); + break; + case RXOverrun: + DBGERR("Data overrun\n"); + break; + case TXUnderrun: + DBGERR("Data underrun\n"); + break; + case StartBitFail: + DBGERR("Data start bit Fail\n"); + break; + } + return false; +} + +// +// Class ClockController +// + +/** + * \internal + * This class controls the clock speed of the SDIO peripheral. The SDIO + * peripheral, when used in polled mode, requires two timing critical pieces of + * code: the one to send and the one to receive a data block. This because + * the peripheral has a 128 byte fifo while the block size is 512 byte, and + * if fifo underrun/overrun occurs the peripheral does not pause communcation, + * instead it simply aborts the data transfer. Since the speed of the code to + * read/write a data block depends on too many factors, such as compiler + * optimizations, code running from internal flash or external ram, and the + * cpu clock speed, a dynamic clocking approach was chosen. + */ +class ClockController +{ +public: + + /** + * \internal. Set a low clock speed of 400KHz or less, used for + * detecting SD/MMC cards. This function as a side effect enables 1bit bus + * width, and disables clock powersave, since it is not allowed by SD spec. + */ + static void setLowSpeedClock() + { + clockReductionAvailable=0; + // No hardware flow control, SDIO_CK generated on rising edge, 1bit bus + // width, no clock bypass, no powersave. + // Set low clock speed 400KHz, 72MHz/400KHz-2=178 + SDIO->CLKCR=CLOCK_400KHz | SDIO_CLKCR_CLKEN; + SDIO->DTIMER=240000; //Timeout 600ms expressed in SD_CK cycles + } + + /** + * \internal + * Automatically select the data speed. + * Since the maximum speed depends on many factors, such as code running in + * internal or external RAM, compiler optimizations etc. this routine + * selects the highest sustainable data transfer speed. + * This is done by binary search until the highest clock speed that causes + * no errors is found. + * This function as a side effect enables 4bit bus width, and clock + * powersave. + */ + static void calibrateClockSpeed(SDIODriver *sdio); + + /** + * \internal + * Since clock speed is set dynamically by bynary search at runtime, a + * corner case might be that of a clock speed which results in unreliable + * data transfer, that sometimes succeeds, and sometimes fail. + * For maximum robustness, this function is provided to reduce the clock + * speed slightly in case a data transfer should fail after clock + * calibration. To avoid inadvertently considering other kind of issues as + * clock issues, this function can be called only MAX_ALLOWED_REDUCTIONS + * times after clock calibration, subsequent calls will fail. This will + * avoid other issues causing an ever decreasing clock speed. + * \return true on success, false on failure + */ + static bool reduceClockSpeed() { return IRQreduceClockSpeed(); } + + /** + * \internal + * Same as reduceClockSpeed(), can be called with interrupts disabled. + * \return true on success, false on failure + */ + static bool IRQreduceClockSpeed(); + + /** + * \internal + * Read and write operation do retry during normal use for robustness, but + * during clock claibration they must not retry for speed reasons. This + * member function returns 1 during clock claibration and MAX_RETRY during + * normal use. + */ + static unsigned char getRetryCount() { return retries; } + +private: + /** + * Set SDIO clock speed + * \param clkdiv speed is SDIOCLK/(clkdiv+2) + */ + static void setClockSpeed(unsigned int clkdiv); + + /** + * \internal + * Value of SDIO->CLKCR that will give a 400KHz clock, depending on cpu + * clock speed. + */ + #ifdef SYSCLK_FREQ_72MHz + static const unsigned int CLOCK_400KHz=178; + #elif SYSCLK_FREQ_56MHz + static const unsigned int CLOCK_400KHz=138; + #elif SYSCLK_FREQ_48MHz + static const unsigned int CLOCK_400KHz=118; + #elif SYSCLK_FREQ_36MHz + static const unsigned int CLOCK_400KHz=88; + #elif SYSCLK_FREQ_24MHz + static const unsigned int CLOCK_400KHz=58; + #else + static const unsigned int CLOCK_400KHz=18; + #endif + + ///\internal Clock enabled, bus width 4bit, clock powersave enabled. + static const unsigned int CLKCR_FLAGS=SDIO_CLKCR_CLKEN | + SDIO_CLKCR_WIDBUS_0 | SDIO_CLKCR_PWRSAV; + + ///\internal Maximum number of calls to IRQreduceClockSpeed() allowed + ///When using polled mode this is a critical parameter, if SDIO driver + ///starts to fail, it might be a good idea to increase this + static const unsigned char MAX_ALLOWED_REDUCTIONS=7; + + ///\internal value returned by getRetryCount() while *not* calibrating clock. + ///When using polled mode this is a critical parameter, if SDIO driver + ///starts to fail, it might be a good idea to increase this + static const unsigned char MAX_RETRY=10; + + ///\internal Used to allow only one call to reduceClockSpeed() + static unsigned char clockReductionAvailable; + + static unsigned char retries; +}; + +void ClockController::calibrateClockSpeed(SDIODriver *sdio) +{ + //During calibration we call readBlock which will call reduceClockSpeed() + //so not to invalidate calibration clock reduction must not be available + clockReductionAvailable=0; + retries=1; + + DBG("Automatic speed calibration\n"); + unsigned int buffer[512/sizeof(unsigned int)]; + unsigned int minFreq=CLOCK_400KHz; //400KHz, independent of CPU clock + unsigned int maxFreq=1; //24MHz with CPU running @ 72MHz + unsigned int selected; + while(minFreq-maxFreq>1) + { + selected=(minFreq+maxFreq)/2; + DBG("Trying CLKCR=%d\n",selected); + setClockSpeed(selected); + if(sdio->readBlock(reinterpret_cast(buffer),512,0)==512) + minFreq=selected; + else maxFreq=selected; + } + //Last round of algorithm + setClockSpeed(maxFreq); + if(sdio->readBlock(reinterpret_cast(buffer),512,0)==512) + { + DBG("Optimal CLKCR=%d\n",maxFreq); + } else { + setClockSpeed(minFreq); + DBG("Optimal CLKCR=%d\n",minFreq); + } + + //Make clock reduction available + clockReductionAvailable=MAX_ALLOWED_REDUCTIONS; + retries=MAX_RETRY; +} + +bool ClockController::IRQreduceClockSpeed() +{ + //Ensure this function can be called only twice per calibration + if(clockReductionAvailable==0) return false; + clockReductionAvailable--; + + unsigned int currentClkcr=SDIO->CLKCR & 0xff; + if(currentClkcr==CLOCK_400KHz) return false; //No lower than this value + + //If the value of clockcr is low, increasing it by one is enough since + //frequency changes a lot, otherwise increase by 2. + if(currentClkcr<10) currentClkcr++; + else currentClkcr+=2; + + setClockSpeed(currentClkcr); + return true; +} + +void ClockController::setClockSpeed(unsigned int clkdiv) +{ + SDIO->CLKCR=clkdiv | CLKCR_FLAGS; + //Timeout 600ms expressed in SD_CK cycles + SDIO->DTIMER=(6*SystemCoreClock)/((clkdiv+2)*10); +} + +unsigned char ClockController::clockReductionAvailable=false; +unsigned char ClockController::retries=ClockController::MAX_RETRY; + +// +// Data send/receive functions +// + +/** + * \internal + * Wait until the card is ready for data transfer. + * Can be called independently of the card being selected. + * \return true on success, false on failure + */ +static bool waitForCardReady() +{ + const int timeout=1500; //Timeout 1.5 second + const int sleepTime=2; + for(int i=0;iDLEN and must match the size parameter given to this + * function. + * \param buffer buffer where to store received data. Its size must be >=size + * \param buffer size, which *must* be multiple of 8 words (32bytes) + * Note that the size parameter must be expressed in word (4bytes), while + * the value in SDIO->DLEN is expressed in bytes. + * \return a DataResult object + */ +static DataResult IRQreceiveDataBlock(unsigned int *buffer, unsigned int size) +{ + // A note on speed. + // Due to the auto calibration of SDIO clock speed being done with + // IRQreceiveDataBlock(), the speed of this function must be comparable + // with the speed of IRQsendDataBlock(), otherwise IRQsendDataBlock() + // will fail because of data underrun. + const unsigned int *bufend=buffer+size; + unsigned int status; + for(;;) + { + status=SDIO->STA; + if(status & (SDIO_STA_RXOVERR | SDIO_STA_DCRCFAIL | + SDIO_STA_DTIMEOUT | SDIO_STA_STBITERR | SDIO_STA_DBCKEND)) break; + if((status & SDIO_STA_RXFIFOHF) && (buffer!=bufend)) + { + //Read 8 words from the fifo, loop entirely unrolled for speed + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + *buffer=SDIO->FIFO; buffer++; + } + } + SDIO->ICR=0x7ff;//Clear flags + if(status & SDIO_STA_RXOVERR) return DataResult(DataResult::RXOverrun); + if(status & SDIO_STA_DCRCFAIL) return DataResult(DataResult::CRCFail); + if(status & SDIO_STA_DTIMEOUT) return DataResult(DataResult::Timeout); + if(status & SDIO_STA_STBITERR) return DataResult(DataResult::StartBitFail); + //Read eventual data left in the FIFO + for(;;) + { + if((SDIO->STA & SDIO_STA_RXDAVL)==0) break; + *buffer=SDIO->FIFO; buffer++; + } + return DataResult(DataResult::Ok); +} + +/** + * \internal + * Send a data block. The end of the data block must be told to the SDIO + * peripheral in SDIO->DLEN and must match the size parameter given to this + * function. + * \param buffer buffer where to store received data. Its size must be >=size + * \param buffer size, which *must* be multiple of 8 words (32bytes). + * Note that the size parameter must be expressed in word (4bytes), while + * the value in SDIO->DLEN is expressed in bytes. + * \return a DataResult object + */ +static DataResult IRQsendDataBlock(const unsigned int *buffer, unsigned int size) +{ + // A note on speed. + // Due to the auto calibration of SDIO clock speed being done with + // IRQreceiveDataBlock(), the speed of this function must be comparable + // with the speed of IRQreceiveDataBlock(), otherwise this function + // will fail because of data underrun. + const unsigned int *bufend=buffer+size; + unsigned int status; + for(;;) + { + status=SDIO->STA; + if(status & (SDIO_STA_TXUNDERR | SDIO_STA_DCRCFAIL | + SDIO_STA_DTIMEOUT | SDIO_STA_STBITERR | SDIO_STA_DBCKEND)) break; + if((status & SDIO_STA_TXFIFOHE) && (buffer!=bufend)) + { + //Write 8 words to the fifo, loop entirely unrolled for speed + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + SDIO->FIFO=*buffer; buffer++; + } + } + SDIO->ICR=0x7ff;//Clear flags + if(status & SDIO_STA_TXUNDERR) return DataResult(DataResult::TXUnderrun); + if(status & SDIO_STA_DCRCFAIL) return DataResult(DataResult::CRCFail); + if(status & SDIO_STA_DTIMEOUT) return DataResult(DataResult::Timeout); + if(status & SDIO_STA_STBITERR) return DataResult(DataResult::StartBitFail); + return DataResult(DataResult::Ok); +} + +/** + * \internal + * Read a single block of 512 bytes from an SD/MMC card. + * Card must be selected prior to caling this function. + * \param buffer, a buffer whose size is >=512 bytes, word aligned + * \param lba logical block address of the block to read. + */ +static bool singleBlockRead(unsigned int *buffer, unsigned int lba) +{ + if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC + + if(waitForCardReady()==false) return false; + + CmdResult cr; + DataResult dr; + bool failed=true; + for(;;) + { + // Since we read with polling, a context switch or interrupt here + // would cause a fifo overrun, so we disable interrupts. + FastInterruptDisableLock dLock; + + SDIO->DLEN=512; + //Block size 512 bytes, block data xfer, from card to controller + SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN; + + cr=Command::IRQsend(Command::CMD17,lba); + if(cr.IRQvalidateR1Response()) + { + dr=IRQreceiveDataBlock(buffer,512/sizeof(unsigned int)); + SDIO->DCTRL=0; //Disable data path state machine + + //If failed because too slow check if it is possible to reduce speed + if(dr.getError()==DataResult::RXOverrun) + { + if(ClockController::IRQreduceClockSpeed()) + { + //Disabling interrupts for too long is bad + FastInterruptEnableLock eLock(dLock); + //After an error during data xfer the card might be a little + //confused. So send STOP_TRANSMISSION command to reassure it + cr=Command::send(Command::CMD12,0); + if(cr.validateR1Response()) continue; + } + } + + if(dr.getError()==DataResult::Ok) failed=false; + } + break; + } + if(failed) + { + cr.validateR1Response(); + dr.validateError(); + //After an error during data xfer the card might be a little + //confused. So send STOP_TRANSMISSION command to reassure it + cr=Command::send(Command::CMD12,0); + cr.validateR1Response(); + return false; + } + return true; +} + +/** + * \internal + * Write a single block of 512 bytes to an SD/MMC card + * Card must be selected prior to caling this function. + * \param buffer, a buffer whose size is >=512 bytes + * \param lba logical block address of the block to write. + */ +static bool singleBlockWrite(const unsigned int *buffer, unsigned int lba) +{ + if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC + + if(waitForCardReady()==false) return false; + + bool failed=true; + CmdResult cr; + DataResult dr; + for(;;) + { + // Since we write with polling, a context switch or interrupt here + // would cause a fifo overrun, so we disable interrupts. + FastInterruptDisableLock dLock; + + cr=Command::IRQsend(Command::CMD24,lba); + if(cr.IRQvalidateR1Response()) + { + SDIO->DLEN=512; + //Block size 512 bytes, block data xfer, from controller to card + SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DTEN; + + dr=IRQsendDataBlock(buffer,512/sizeof(unsigned int)); + SDIO->DCTRL=0; //Disable data path state machine + + //If failed because too slow check if it is possible to reduce speed + if(dr.getError()==DataResult::TXUnderrun) + { + if(ClockController::IRQreduceClockSpeed()) + { + //Disabling interrupts for too long is bad + FastInterruptEnableLock eLock(dLock); + //After an error during data xfer the card might be a little + //confused. So send STOP_TRANSMISSION command to reassure it + cr=Command::send(Command::CMD12,0); + if(cr.validateR1Response()) continue; + } + } + + if(dr.getError()==DataResult::Ok) failed=false; + } + break; + } + if(failed) + { + cr.validateR1Response(); + dr.validateError(); + //After an error during data xfer the card might be a little + //confused. So send STOP_TRANSMISSION command to reassure it + cr=Command::send(Command::CMD12,0); + cr.validateR1Response(); + return false; + } + return true; +} + +#else //__ENABLE_XRAM + +/** + * \internal + * Prints the errors that may occur during a DMA transfer + */ +static void displayBlockTransferError() +{ + DBGERR("Block transfer error\n"); + if(dmaFlags & DMA_ISR_TEIF4) DBGERR("* DMA Transfer error\n"); + if(sdioFlags & SDIO_STA_STBITERR) DBGERR("* SDIO Start bit error\n"); + if(sdioFlags & SDIO_STA_RXOVERR) DBGERR("* SDIO RX Overrun\n"); + if(sdioFlags & SDIO_STA_TXUNDERR) DBGERR("* SDIO TX Underrun error\n"); + if(sdioFlags & SDIO_STA_DCRCFAIL) DBGERR("* SDIO Data CRC fail\n"); + if(sdioFlags & SDIO_STA_DTIMEOUT) DBGERR("* SDIO Data timeout\n"); +} + +/** + * \internal + * Read a given number of contiguous 512 byte blocks from an SD/MMC card. + * Card must be selected prior to calling this function. + * \param buffer, a buffer whose size is 512*nblk bytes, word aligned + * \param nblk number of blocks to read. + * \param lba logical block address of the first block to read. + */ +static bool multipleBlockRead(unsigned int *buffer, unsigned int nblk, + unsigned int lba) +{ + if(nblk==0) return true; + while(nblk>511) + { + if(multipleBlockRead(buffer,511,lba)==false) return false; + buffer+=511*512; + nblk-=511; + lba+=511; + } + if(waitForCardReady()==false) return false; + + if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC + + //Clear both SDIO and DMA interrupt flags + SDIO->ICR=0x7ff; + DMA2->IFCR=DMA_IFCR_CGIF4; + + transferError=false; + dmaFlags=sdioFlags=0; + waiting=Thread::getCurrentThread(); + + //Data transfer is considered complete once the DMA transfer complete + //interrupt occurs, that happens when the last data was written in the + //buffer. Both SDIO and DMA error interrupts are active to catch errors + SDIO->MASK=SDIO_MASK_STBITERRIE | //Interrupt on start bit error + SDIO_MASK_RXOVERRIE | //Interrupt on rx underrun + SDIO_MASK_TXUNDERRIE | //Interrupt on tx underrun + SDIO_MASK_DCRCFAILIE | //Interrupt on data CRC fail + SDIO_MASK_DTIMEOUTIE; //Interrupt on data timeout + DMA2_Channel4->CPAR=reinterpret_cast(&SDIO->FIFO); + DMA2_Channel4->CMAR=reinterpret_cast(buffer); + DMA2_Channel4->CNDTR=nblk*512/sizeof(unsigned int); + DMA2_Channel4->CCR=DMA_CCR4_PL_1 | //High priority DMA stream + DMA_CCR4_MSIZE_1 | //Write 32bit at a time to RAM + DMA_CCR4_PSIZE_1 | //Read 32bit at a time from SDIO + DMA_CCR4_MINC | //Increment RAM pointer + 0 | //Peripheral to memory direction + DMA_CCR4_TCIE | //Interrupt on transfer complete + DMA_CCR4_TEIE | //Interrupt on transfer error + DMA_CCR4_EN; //Start the DMA + + SDIO->DLEN=nblk*512; + if(waiting==0) + { + DBGERR("Premature wakeup\n"); + transferError=true; + } + CmdResult cr=Command::send(nblk>1 ? Command::CMD18 : Command::CMD17,lba); + if(cr.validateR1Response()) + { + //Block size 512 bytes, block data xfer, from card to controller + SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN; + FastInterruptDisableLock dLock; + while(waiting) + { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } else transferError=true; + DMA2_Channel4->CCR=0; + while(DMA2_Channel4->CCR & DMA_CCR4_EN) ; //DMA may take time to stop + SDIO->DCTRL=0; //Disable data path state machine + SDIO->MASK=0; + + // CMD12 is sent to end CMD18 (multiple block read), or to abort an + // unfinished read in case of errors + if(nblk>1 || transferError) cr=Command::send(Command::CMD12,0); + if(transferError || cr.validateR1Response()==false) + { + displayBlockTransferError(); + ClockController::reduceClockSpeed(); + return false; + } + return true; +} + +/** + * \internal + * Write a given number of contiguous 512 byte blocks to an SD/MMC card. + * Card must be selected prior to calling this function. + * \param buffer, a buffer whose size is 512*nblk bytes, word aligned + * \param nblk number of blocks to write. + * \param lba logical block address of the first block to write. + */ +static bool multipleBlockWrite(const unsigned int *buffer, unsigned int nblk, + unsigned int lba) +{ + if(nblk==0) return true; + while(nblk>511) + { + if(multipleBlockWrite(buffer,511,lba)==false) return false; + buffer+=511*512; + nblk-=511; + lba+=511; + } + if(waitForCardReady()==false) return false; + + if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC + if(nblk>1) + { + CmdResult cr=Command::send(Command::ACMD23,nblk); + if(cr.validateR1Response()==false) return false; + } + + //Clear both SDIO and DMA interrupt flags + SDIO->ICR=0x7ff; + DMA2->IFCR=DMA_IFCR_CGIF4; + + transferError=false; + dmaFlags=sdioFlags=0; + waiting=Thread::getCurrentThread(); + + //Data transfer is considered complete once the SDIO transfer complete + //interrupt occurs, that happens when the last data was written to the SDIO + //Both SDIO and DMA error interrupts are active to catch errors + SDIO->MASK=SDIO_MASK_DATAENDIE | //Interrupt on data end + SDIO_MASK_STBITERRIE | //Interrupt on start bit error + SDIO_MASK_RXOVERRIE | //Interrupt on rx underrun + SDIO_MASK_TXUNDERRIE | //Interrupt on tx underrun + SDIO_MASK_DCRCFAILIE | //Interrupt on data CRC fail + SDIO_MASK_DTIMEOUTIE; //Interrupt on data timeout + DMA2_Channel4->CPAR=reinterpret_cast(&SDIO->FIFO); + DMA2_Channel4->CMAR=reinterpret_cast(buffer); + DMA2_Channel4->CNDTR=nblk*512/sizeof(unsigned int); + DMA2_Channel4->CCR=DMA_CCR4_PL_1 | //High priority DMA stream + DMA_CCR4_MSIZE_1 | //Read 32bit at a time from RAM + DMA_CCR4_PSIZE_1 | //Write 32bit at a time to SDIO + DMA_CCR4_MINC | //Increment RAM pointer + DMA_CCR4_DIR | //Memory to peripheral direction + DMA_CCR4_TEIE | //Interrupt on transfer error + DMA_CCR4_EN; //Start the DMA + + SDIO->DLEN=nblk*512; + if(waiting==0) + { + DBGERR("Premature wakeup\n"); + transferError=true; + } + CmdResult cr=Command::send(nblk>1 ? Command::CMD25 : Command::CMD24,lba); + if(cr.validateR1Response()) + { + //Block size 512 bytes, block data xfer, from card to controller + SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTEN; + FastInterruptDisableLock dLock; + while(waiting) + { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } else transferError=true; + DMA2_Channel4->CCR=0; + while(DMA2_Channel4->CCR & DMA_CCR4_EN) ; //DMA may take time to stop + SDIO->DCTRL=0; //Disable data path state machine + SDIO->MASK=0; + + // CMD12 is sent to end CMD25 (multiple block write), or to abort an + // unfinished write in case of errors + if(nblk>1 || transferError) cr=Command::send(Command::CMD12,0); + if(transferError || cr.validateR1Response()==false) + { + displayBlockTransferError(); + ClockController::reduceClockSpeed(); + return false; + } + return true; +} +#endif //__ENABLE_XRAM + +// +// Class CardSelector +// + +/** + * \internal + * Simple RAII class for selecting an SD/MMC card an automatically deselect it + * at the end of the scope. + */ +class CardSelector +{ +public: + /** + * \internal + * Constructor. Selects the card. + * The result of the select operation is available through its succeded() + * member function + */ + explicit CardSelector() + { + success=Command::send( + Command::CMD7,Command::getRca()<<16).validateR1Response(); + } + + /** + * \internal + * \return true if the card was selected, false on error + */ + bool succeded() { return success; } + + /** + * \internal + * Destructor, ensures that the card is deselected + */ + ~CardSelector() + { + Command::send(Command::CMD7,0); //Deselect card. This will timeout + } + +private: + bool success; +}; + +// +// Initialization helper functions +// + +/** + * \internal + * Initialzes the SDIO peripheral in the STM32 + */ +static void initSDIOPeripheral() +{ + { + //Doing read-modify-write on RCC->APBENR2 and gpios, better be safe + FastInterruptDisableLock lock; + RCC->APB2ENR |= RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPDEN; + RCC_SYNC(); + #ifdef __ENABLE_XRAM + RCC->AHBENR |= RCC_AHBENR_SDIOEN; + #else //__ENABLE_XRAM + RCC->AHBENR |= RCC_AHBENR_SDIOEN | RCC_AHBENR_DMA2EN; + #endif //__ENABLE_XRAM + RCC_SYNC(); + sdD0::mode(Mode::ALTERNATE); + sdD1::mode(Mode::ALTERNATE); + sdD2::mode(Mode::ALTERNATE); + sdD3::mode(Mode::ALTERNATE); + sdCLK::mode(Mode::ALTERNATE); + sdCMD::mode(Mode::ALTERNATE); + } + #ifndef __ENABLE_XRAM + NVIC_SetPriority(DMA2_Channel4_5_IRQn,15);//Low priority for DMA + NVIC_EnableIRQ(DMA2_Channel4_5_IRQn); + NVIC_SetPriority(SDIO_IRQn,15);//Low priority for SDIO + NVIC_EnableIRQ(SDIO_IRQn); + #endif //__ENABLE_XRAM + + SDIO->POWER=0; //Power off state + delayUs(1); + SDIO->CLKCR=0; + SDIO->CMD=0; + SDIO->DCTRL=0; + SDIO->ICR=0xc007ff; + SDIO->POWER=SDIO_POWER_PWRCTRL_1 | SDIO_POWER_PWRCTRL_0; //Power on state + //This delay is particularly important: when setting the POWER register a + //glitch on the CMD pin happens. This glitch has a fast fall time and a slow + //rise time resembling an RC charge with a ~6us rise time. If the clock is + //started too soon, the card sees a clock pulse while CMD is low, and + //interprets it as a start bit. No, setting POWER to powerup does not + //eliminate the glitch. + delayUs(10); + ClockController::setLowSpeedClock(); +} + +/** + * \internal + * Detect if the card is an SDHC, SDv2, SDv1, MMC + * \return Type of card: (1<<0)=MMC (1<<1)=SDv1 (1<<2)=SDv2 (1<<2)|(1<<3)=SDHC + * or Invalid if card detect failed. + */ +static CardType detectCardType() +{ + const int INIT_TIMEOUT=200; //200*10ms= 2 seconds + CmdResult r=Command::send(Command::CMD8,0x1aa); + if(r.validateError()) + { + //We have an SDv2 card connected + if(r.getResponse()!=0x1aa) + { + DBGERR("CMD8 validation: voltage range fail\n"); + return Invalid; + } + for(int i=0;i SDIODriver::instance() +{ + static FastMutex m; + static intrusive_ref_ptr instance; + Lock l(m); + if(!instance) instance=new SDIODriver(); + return instance; +} + +ssize_t SDIODriver::readBlock(void* buffer, size_t size, off_t where) +{ + if(where % 512 || size % 512) return -EFAULT; + unsigned int lba=where/512; + unsigned int nSectors=size/512; + Lock l(mutex); + DBG("SDIODriver::readBlock(): nSectors=%d\n",nSectors); + bool aligned=BufferConverter::isWordAligned(buffer); + if(aligned==false) DBG("Buffer misaligned\n"); + + for(int i=0;i(buffer); + unsigned int tempLba=lba; + for(unsigned int j=0;j(buffer), + nSectors,lba)==false) error=true; + } else { + unsigned char *tempBuffer=reinterpret_cast(buffer); + unsigned int tempLba=lba; + for(unsigned int j=0;j0) DBGERR("Read: required %d retries\n",i); + return size; + } + } + return -EBADF; +} + +ssize_t SDIODriver::writeBlock(const void* buffer, size_t size, off_t where) +{ + if(where % 512 || size % 512) return -EFAULT; + unsigned int lba=where/512; + unsigned int nSectors=size/512; + Lock l(mutex); + DBG("SDIODriver::writeBlock(): nSectors=%d\n",nSectors); + bool aligned=BufferConverter::isWordAligned(buffer); + if(aligned==false) DBG("Buffer misaligned\n"); + + for(int i=0;i(buffer); + unsigned int tempLba=lba; + for(unsigned int j=0;j(buffer), + nSectors,lba)==false) error=true; + } else { + const unsigned char *tempBuffer= + reinterpret_cast(buffer); + unsigned int tempLba=lba; + for(unsigned int j=0;j0) DBGERR("Write: required %d retries\n",i); + return size; + } + } + return -EBADF; +} + +int SDIODriver::ioctl(int cmd, void* arg) +{ + DBG("SDIODriver::ioctl()\n"); + if(cmd!=IOCTL_SYNC) return -ENOTTY; + Lock l(mutex); + //Note: no need to select card, since status can be queried even with card + //not selected. + return waitForCardReady() ? 0 : -EFAULT; +} + +SDIODriver::SDIODriver() : Device(Device::BLOCK) +{ + initSDIOPeripheral(); + + // This is more important than it seems, since CMD55 requires the card's RCA + // as argument. During initalization, after CMD0 the card has an RCA of zero + // so without this line ACMD41 will fail and the card won't be initialized. + Command::setRca(0); + + //Send card reset command + CmdResult r=Command::send(Command::CMD0,0); + if(r.validateError()==false) return; + + cardType=detectCardType(); + if(cardType==Invalid) return; //Card detect failed + if(cardType==MMC) return; //MMC cards currently unsupported + + // Now give an RCA to the card. In theory we should loop and enumerate all + // the cards but this driver supports only one card. + r=Command::send(Command::CMD2,0); + //CMD2 sends R2 response, whose CMDINDEX field is wrong + if(r.getError()!=CmdResult::Ok && r.getError()!=CmdResult::RespNotMatch) + { + r.validateError(); + return; + } + r=Command::send(Command::CMD3,0); + if(r.validateR6Response()==false) return; + Command::setRca(r.getResponse()>>16); + DBG("Got RCA=%u\n",Command::getRca()); + if(Command::getRca()==0) + { + //RCA=0 can't be accepted, since it is used to deselect cards + DBGERR("RCA=0 is invalid\n"); + return; + } + + //Lastly, try selecting the card and configure the latest bits + { + CardSelector selector; + if(selector.succeded()==false) return; + + r=Command::send(Command::CMD13,Command::getRca()<<16);//Get status + if(r.validateR1Response()==false) return; + if(r.getState()!=4) //4=Tran state + { + DBGERR("CMD7 was not able to select card\n"); + return; + } + + r=Command::send(Command::ACMD6,2); //Set 4 bit bus width + if(r.validateR1Response()==false) return; + + if(cardType!=SDHC) + { + r=Command::send(Command::CMD16,512); //Set 512Byte block length + if(r.validateR1Response()==false) return; + } + } + + // Now that card is initialized, perform self calibration of maximum + // possible read/write speed. This as a side effect enables 4bit bus width. + ClockController::calibrateClockSpeed(this); + + DBG("SDIO init: Success\n"); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.h b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.h new file mode 100644 index 00000000..f9b0627d --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f1.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SD_STM32F1_H +#define SD_STM32F1_H + +#include "kernel/sync.h" +#include "filesystem/devfs/devfs.h" +#include "filesystem/ioctl.h" + +namespace miosix { + +/** + * Driver for the SDIO peripheral in STM32F1 microcontrollers + */ +class SDIODriver : public Device +{ +public: + /** + * \return an instance to this class, singleton + */ + static intrusive_ref_ptr instance(); + + virtual ssize_t readBlock(void *buffer, size_t size, off_t where); + + virtual ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + virtual int ioctl(int cmd, void *arg); +private: + /** + * Constructor + */ + SDIODriver(); + + FastMutex mutex; +}; + +} //namespace miosix + +#endif //SD_STM32F1_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f2_f4.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f2_f4.cpp new file mode 100644 index 00000000..da9e577e --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f2_f4.cpp @@ -0,0 +1,1478 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "sd_stm32f2_f4.h" +#include "interfaces/bsp.h" +#include "interfaces/arch_registers.h" +#include "core/cache_cortexMx.h" +#include "kernel/scheduler/scheduler.h" +#include "interfaces/delays.h" +#include "kernel/kernel.h" +#include "board_settings.h" //For sdVoltage and SD_ONE_BIT_DATABUS definitions +#include +#include +#include + +//Note: enabling debugging might cause deadlock when using sleep() or reboot() +//The bug won't be fixed because debugging is only useful for driver development +///\internal Debug macro, for normal conditions +//#define DBG iprintf +#define DBG(x,...) do {} while(0) +///\internal Debug macro, for errors only +//#define DBGERR iprintf +#define DBGERR(x,...) do {} while(0) + +/* + * The SDMMC1 peripheral in the STM32F7 is basically the old SDIO with the + * registers renamed and a few bits changed. Let's map the old names in the new + */ +#if defined(_ARCH_CORTEXM7_STM32F7) || defined(_ARCH_CORTEXM7_STM32H7) + +#define SDIO SDMMC1 +#define RCC_APB2ENR_SDIOEN RCC_APB2ENR_SDMMC1EN +#define SDIO_IRQn SDMMC1_IRQn + +#define SDIO_STA_STBITERR 0 //This bit has been removed +#define SDIO_STA_RXOVERR SDMMC_STA_RXOVERR +#define SDIO_STA_TXUNDERR SDMMC_STA_TXUNDERR +#define SDIO_STA_DTIMEOUT SDMMC_STA_DTIMEOUT +#define SDIO_STA_DCRCFAIL SDMMC_STA_DCRCFAIL +#define SDIO_STA_CMDSENT SDMMC_STA_CMDSENT +#define SDIO_STA_CMDREND SDMMC_STA_CMDREND +#define SDIO_STA_CCRCFAIL SDMMC_STA_CCRCFAIL +#define SDIO_STA_CTIMEOUT SDMMC_STA_CTIMEOUT + +#define SDIO_CMD_CPSMEN SDMMC_CMD_CPSMEN +#define SDIO_CMD_WAITRESP_0 SDMMC_CMD_WAITRESP_0 +#define SDIO_CMD_WAITRESP_1 SDMMC_CMD_WAITRESP_1 + +#define SDIO_ICR_CTIMEOUTC SDMMC_ICR_CTIMEOUTC +#define SDIO_ICR_CCRCFAILC SDMMC_ICR_CCRCFAILC + +#define SDIO_CLKCR_CLKEN SDMMC_CLKCR_CLKEN +#define SDIO_CLKCR_PWRSAV SDMMC_CLKCR_PWRSAV +#define SDIO_CLKCR_PWRSAV SDMMC_CLKCR_PWRSAV + +#define SDIO_MASK_STBITERRIE 0 //This bit has been removed +#define SDIO_MASK_RXOVERRIE SDMMC_MASK_RXOVERRIE +#define SDIO_MASK_TXUNDERRIE SDMMC_MASK_TXUNDERRIE +#define SDIO_MASK_DCRCFAILIE SDMMC_MASK_DCRCFAILIE +#define SDIO_MASK_DTIMEOUTIE SDMMC_MASK_DTIMEOUTIE +#define SDIO_MASK_DATAENDIE SDMMC_MASK_DATAENDIE + +#define SDIO_DCTRL_DMAEN SDMMC_DCTRL_DMAEN +#define SDIO_DCTRL_DTDIR SDMMC_DCTRL_DTDIR +#define SDIO_DCTRL_DTEN SDMMC_DCTRL_DTEN + +#define SDIO_POWER_PWRCTRL_1 SDMMC_POWER_PWRCTRL_1 +#define SDIO_POWER_PWRCTRL_0 SDMMC_POWER_PWRCTRL_0 + +#endif //defined(_ARCH_CORTEXM7_STM32F7) || defined(_ARCH_CORTEXM7_STM32H7) + +/** + * \internal + * DMA2 Stream3 interrupt handler + */ +void __attribute__((naked)) DMA2_Stream3_IRQHandler() +{ + saveContext(); + asm volatile("bl _ZN6miosix18DMA2stream3irqImplEv"); + restoreContext(); +} + +/** + * \internal + * SDIO interrupt handler + */ +#if defined(_ARCH_CORTEXM7_STM32F7) || defined(_ARCH_CORTEXM7_STM32H7) +void __attribute__((naked)) SDMMC1_IRQHandler() +#else //stm32f2 and stm32f4 +void __attribute__((naked)) SDIO_IRQHandler() +#endif +{ + saveContext(); + asm volatile("bl _ZN6miosix11SDIOirqImplEv"); + restoreContext(); +} + +namespace miosix { + +static volatile bool transferError; ///< \internal DMA or SDIO transfer error +static Thread *waiting; ///< \internal Thread waiting for transfer +static unsigned int dmaFlags; ///< \internal DMA status flags +static unsigned int sdioFlags; ///< \internal SDIO status flags + +/** + * \internal + * DMA2 Stream3 interrupt handler actual implementation + */ +void __attribute__((used)) DMA2stream3irqImpl() +{ + dmaFlags=DMA2->LISR; + if(dmaFlags & (DMA_LISR_TEIF3 | DMA_LISR_DMEIF3 | DMA_LISR_FEIF3)) + transferError=true; + + DMA2->LIFCR=DMA_LIFCR_CTCIF3 | + DMA_LIFCR_CTEIF3 | + DMA_LIFCR_CDMEIF3 | + DMA_LIFCR_CFEIF3; + + if(!waiting) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * \internal + * DMA2 Stream3 interrupt handler actual implementation + */ +void __attribute__((used)) SDIOirqImpl() +{ + sdioFlags=SDIO->STA; + if(sdioFlags & (SDIO_STA_STBITERR | SDIO_STA_RXOVERR | + SDIO_STA_TXUNDERR | SDIO_STA_DTIMEOUT | SDIO_STA_DCRCFAIL)) + transferError=true; + + SDIO->ICR=0x7ff;//Clear flags + + if(!waiting) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/* + * Operating voltage of device. It is sent to the SD card to check if it can + * work at this voltage. Range *must* be within 28..36 + * Example 33=3.3v + */ +//static const unsigned char sdVoltage=33; //Is defined in board_settings.h +static const unsigned int sdVoltageMask=1<<(sdVoltage-13); //See OCR reg in SD spec + +/** + * \internal + * Possible state of the cardType variable. + */ +enum CardType +{ + Invalid=0, ///<\internal Invalid card type + MMC=1<<0, ///<\internal if(cardType==MMC) card is an MMC + SDv1=1<<1, ///<\internal if(cardType==SDv1) card is an SDv1 + SDv2=1<<2, ///<\internal if(cardType==SDv2) card is an SDv2 + SDHC=1<<3 ///<\internal if(cardType==SDHC) card is an SDHC +}; + +///\internal Type of card. +static CardType cardType=Invalid; + +//SD card GPIOs +typedef Gpio sdD0; +typedef Gpio sdD1; +typedef Gpio sdD2; +typedef Gpio sdD3; +typedef Gpio sdCLK; +typedef Gpio sdCMD; + +// +// Class BufferConverter +// + +/** + * \internal + * After fixing the FSMC bug in the stm32f1, ST decided to willingly introduce + * another quirk in the stm32f4. They introduced a core coupled memory that is + * not accessible by the DMA. While from an hardware perspective it may make + * sense, it is a bad design decision when viewed from the software side. + * This is because if application code allocates a buffer in the core coupled + * memory and passes that to an fread() or fwrite() call, that buffer is + * forwarded here, and this driver is DMA-based... Now, in an OS such as Miosix + * that tries to shield the application developer from such quirks, it is + * unacceptable to fail to work in such an use case, so this class exists to + * try and work around this. + * In essence, the first "bad buffer" that is passed to a readBlock() or + * writeBlock() causes the allocation on the heap (which Miosix guarantees + * is not allocated in the core coupled memory) of a 512 byte buffer which is + * then never deallocated and always reused to deal with these bad buffers. + * While this works, performance suffers for two reasons: first, when dealing + * with those bad buffers, the filesystem code is no longer zero copy, and + * second because multiple block read/writes between bad buffers and the SD + * card are implemented as a sequence of single block read/writes. + * If you're an application developer and care about speed, try to allocate + * your buffers in the heap if you're coding for the STM32F4. + */ +class BufferConverter +{ +public: + /** + * \internal + * The buffer will be of this size only. + */ + static const int BUFFER_SIZE=512; + + /** + * \internal + * \return true if the pointer is not inside the CCM + */ + static bool isGoodBuffer(const void *x) + { + unsigned int ptr=reinterpret_cast(x); + return (ptr<0x10000000) || (ptr>=(0x10000000+64*1024)); + } + + /** + * \internal + * Convert from a constunsigned char* buffer of size BUFFER_SIZE to a + * const unsigned int* word aligned buffer. + * If the original buffer is already word aligned it only does a cast, + * otherwise it copies the data on the original buffer to a word aligned + * buffer. Useful if subseqent code will read from the buffer. + * \param a buffer of size BUFFER_SIZE. Can be word aligned or not. + * \return a word aligned buffer with the same data of the given buffer + */ + static const unsigned char *toWordAligned(const unsigned char *buffer); + + /** + * \internal + * Convert from an unsigned char* buffer of size BUFFER_SIZE to an + * unsigned int* word aligned buffer. + * If the original buffer is already word aligned it only does a cast, + * otherwise it returns a new buffer which *does not* contain the data + * on the original buffer. Useful if subseqent code will write to the + * buffer. To move the written data to the original buffer, use + * toOriginalBuffer() + * \param a buffer of size BUFFER_SIZE. Can be word aligned or not. + * \return a word aligned buffer with undefined content. + */ + static unsigned char *toWordAlignedWithoutCopy(unsigned char *buffer); + + /** + * \internal + * Convert the buffer got through toWordAlignedWithoutCopy() to the + * original buffer. If the original buffer was word aligned, nothing + * happens, otherwise a memcpy is done. + * Note that this function does not work on buffers got through + * toWordAligned(). + */ + static void toOriginalBuffer(); + + /** + * \internal + * Can be called to deallocate the buffer + */ + static void deallocateBuffer(); + +private: + static unsigned char *originalBuffer; + static unsigned char *wordAlignedBuffer; +}; + +const unsigned char *BufferConverter::toWordAligned(const unsigned char *buffer) +{ + originalBuffer=0; //Tell toOriginalBuffer() that there's nothing to do + if(isGoodBuffer(buffer)) + { + return buffer; + } else { + if(wordAlignedBuffer==0) + wordAlignedBuffer=new unsigned char[BUFFER_SIZE]; + std::memcpy(wordAlignedBuffer,buffer,BUFFER_SIZE); + return wordAlignedBuffer; + } +} + +unsigned char *BufferConverter::toWordAlignedWithoutCopy( + unsigned char *buffer) +{ + if(isGoodBuffer(buffer)) + { + originalBuffer=0; //Tell toOriginalBuffer() that there's nothing to do + return buffer; + } else { + originalBuffer=buffer; //Save original pointer for toOriginalBuffer() + if(wordAlignedBuffer==0) + wordAlignedBuffer=new unsigned char[BUFFER_SIZE]; + return wordAlignedBuffer; + } +} + +void BufferConverter::toOriginalBuffer() +{ + if(originalBuffer==0) return; + std::memcpy(originalBuffer,wordAlignedBuffer,BUFFER_SIZE); + originalBuffer=0; +} + +void BufferConverter::deallocateBuffer() +{ + originalBuffer=0; //Invalidate also original buffer + if(wordAlignedBuffer!=0) + { + delete[] wordAlignedBuffer; + wordAlignedBuffer=0; + } +} + +unsigned char *BufferConverter::originalBuffer=0; +unsigned char *BufferConverter::wordAlignedBuffer=0; + +// +// Class CmdResult +// + +/** + * \internal + * Contains the result of an SD/MMC command + */ +class CmdResult +{ +public: + + /** + * \internal + * Possible outcomes of sending a command + */ + enum Error + { + Ok=0, /// No errors + Timeout, /// Timeout while waiting command reply + CRCFail, /// CRC check failed in command reply + RespNotMatch,/// Response index does not match command index + ACMDFail /// Sending CMD55 failed + }; + + /** + * \internal + * Default constructor + */ + CmdResult(): cmd(0), error(Ok), response(0) {} + + /** + * \internal + * Constructor, set the response data + * \param cmd command index of command that was sent + * \param result result of command + */ + CmdResult(unsigned char cmd, Error error): cmd(cmd), error(error), + response(SDIO->RESP1) {} + + /** + * \internal + * \return the 32 bit of the response. + * May not be valid if getError()!=Ok or the command does not send a + * response, such as CMD0 + */ + unsigned int getResponse() { return response; } + + /** + * \internal + * \return command index + */ + unsigned char getCmdIndex() { return cmd; } + + /** + * \internal + * \return the error flags of the response + */ + Error getError() { return error; } + + /** + * \internal + * Checks if errors occurred while sending the command. + * \return true if no errors, false otherwise + */ + bool validateError(); + + /** + * \internal + * interprets this->getResponse() as an R1 response, and checks if there are + * errors, or everything is ok + * \return true on success, false on failure + */ + bool validateR1Response(); + + /** + * \internal + * Same as validateR1Response, but can be called with interrupts disabled. + * \return true on success, false on failure + */ + bool IRQvalidateR1Response(); + + /** + * \internal + * interprets this->getResponse() as an R6 response, and checks if there are + * errors, or everything is ok + * \return true on success, false on failure + */ + bool validateR6Response(); + + /** + * \internal + * \return the card state from an R1 or R6 resonse + */ + unsigned char getState(); + +private: + unsigned char cmd; ///<\internal Command index that was sent + Error error; ///<\internal possible error that occurred + unsigned int response; ///<\internal 32bit response +}; + +bool CmdResult::validateError() +{ + switch(error) + { + case Ok: + return true; + case Timeout: + DBGERR("CMD%d: Timeout\n",cmd); + break; + case CRCFail: + DBGERR("CMD%d: CRC Fail\n",cmd); + break; + case RespNotMatch: + DBGERR("CMD%d: Response does not match\n",cmd); + break; + case ACMDFail: + DBGERR("CMD%d: ACMD Fail\n",cmd); + break; + } + return false; +} + +bool CmdResult::validateR1Response() +{ + if(error!=Ok) return validateError(); + //Note: this number is obtained with all the flags of R1 which are errors + //(flagged as E in the SD specification), plus CARD_IS_LOCKED because + //locked card are not supported by this software driver + if((response & 0xfff98008)==0) return true; + DBGERR("CMD%d: R1 response error(s):\n",cmd); + if(response & (1<<31)) DBGERR("Out of range\n"); + if(response & (1<<30)) DBGERR("ADDR error\n"); + if(response & (1<<29)) DBGERR("BLOCKLEN error\n"); + if(response & (1<<28)) DBGERR("ERASE SEQ error\n"); + if(response & (1<<27)) DBGERR("ERASE param\n"); + if(response & (1<<26)) DBGERR("WP violation\n"); + if(response & (1<<25)) DBGERR("card locked\n"); + if(response & (1<<24)) DBGERR("LOCK_UNLOCK failed\n"); + if(response & (1<<23)) DBGERR("command CRC failed\n"); + if(response & (1<<22)) DBGERR("illegal command\n"); + if(response & (1<<21)) DBGERR("ECC fail\n"); + if(response & (1<<20)) DBGERR("card controller error\n"); + if(response & (1<<19)) DBGERR("unknown error\n"); + if(response & (1<<16)) DBGERR("CSD overwrite\n"); + if(response & (1<<15)) DBGERR("WP ERASE skip\n"); + if(response & (1<<3)) DBGERR("AKE_SEQ error\n"); + return false; +} + +bool CmdResult::IRQvalidateR1Response() +{ + if(error!=Ok) return false; + if(response & 0xfff98008) return false; + return true; +} + +bool CmdResult::validateR6Response() +{ + if(error!=Ok) return validateError(); + if((response & 0xe008)==0) return true; + DBGERR("CMD%d: R6 response error(s):\n",cmd); + if(response & (1<<15)) DBGERR("command CRC failed\n"); + if(response & (1<<14)) DBGERR("illegal command\n"); + if(response & (1<<13)) DBGERR("unknown error\n"); + if(response & (1<<3)) DBGERR("AKE_SEQ error\n"); + return false; +} + +unsigned char CmdResult::getState() +{ + unsigned char result=(response>>9) & 0xf; + DBG("CMD%d: State: ",cmd); + switch(result) + { + case 0: DBG("Idle\n"); break; + case 1: DBG("Ready\n"); break; + case 2: DBG("Ident\n"); break; + case 3: DBG("Stby\n"); break; + case 4: DBG("Tran\n"); break; + case 5: DBG("Data\n"); break; + case 6: DBG("Rcv\n"); break; + case 7: DBG("Prg\n"); break; + case 8: DBG("Dis\n"); break; + case 9: DBG("Btst\n"); break; + default: DBG("Unknown\n"); break; + } + return result; +} + +// +// Class Command +// + +/** + * \internal + * This class allows sending commands to an SD or MMC + */ +class Command +{ +public: + + /** + * \internal + * SD/MMC commands + * - bit #7 is @ 1 if a command is an ACMDxx. send() will send the + * sequence CMD55, CMDxx + * - bit from #0 to #5 indicate command index (CMD0..CMD63) + * - bit #6 is don't care + */ + enum CommandType + { + CMD0=0, //GO_IDLE_STATE + CMD2=2, //ALL_SEND_CID + CMD3=3, //SEND_RELATIVE_ADDR + ACMD6=0x80 | 6, //SET_BUS_WIDTH + CMD7=7, //SELECT_DESELECT_CARD + ACMD41=0x80 | 41, //SEND_OP_COND (SD) + CMD8=8, //SEND_IF_COND + CMD9=9, //SEND_CSD + CMD12=12, //STOP_TRANSMISSION + CMD13=13, //SEND_STATUS + CMD16=16, //SET_BLOCKLEN + CMD17=17, //READ_SINGLE_BLOCK + CMD18=18, //READ_MULTIPLE_BLOCK + ACMD23=0x80 | 23, //SET_WR_BLK_ERASE_COUNT (SD) + CMD24=24, //WRITE_BLOCK + CMD25=25, //WRITE_MULTIPLE_BLOCK + CMD55=55 //APP_CMD + }; + + /** + * \internal + * Send a command. + * \param cmd command index (CMD0..CMD63) or ACMDxx command + * \param arg the 32 bit argument to the command + * \return a CmdResult object + */ + static CmdResult send(CommandType cmd, unsigned int arg); + + /** + * \internal + * Set the relative card address, obtained during initialization. + * \param r the card's rca + */ + static void setRca(unsigned short r) { rca=r; } + + /** + * \internal + * \return the card's rca, as set by setRca + */ + static unsigned int getRca() { return static_cast(rca); } + +private: + static unsigned short rca;///<\internal Card's relative address +}; + +CmdResult Command::send(CommandType cmd, unsigned int arg) +{ + unsigned char cc=static_cast(cmd); + //Handle ACMDxx as CMD55, CMDxx + if(cc & 0x80) + { + DBG("ACMD%d\n",cc & 0x3f); + CmdResult r=send(CMD55,(static_cast(rca))<<16); + if(r.validateR1Response()==false) + return CmdResult(cc & 0x3f,CmdResult::ACMDFail); + //Bit 5 @ 1 = next command will be interpreted as ACMD + if((r.getResponse() & (1<<5))==0) + return CmdResult(cc & 0x3f,CmdResult::ACMDFail); + } else DBG("CMD%d\n",cc & 0x3f); + + //Send command + cc &= 0x3f; + unsigned int command=SDIO_CMD_CPSMEN | static_cast(cc); + if(cc!=CMD0) command |= SDIO_CMD_WAITRESP_0; //CMD0 has no response + if(cc==CMD2) command |= SDIO_CMD_WAITRESP_1; //CMD2 has long response + if(cc==CMD9) command |= SDIO_CMD_WAITRESP_1; //CMD9 has long response + SDIO->ARG=arg; + SDIO->CMD=command; + + //CMD0 has no response, so wait until it is sent + if(cc==CMD0) + { + for(int i=0;i<500;i++) + { + if(SDIO->STA & SDIO_STA_CMDSENT) + { + SDIO->ICR=0x7ff;//Clear flags + return CmdResult(cc,CmdResult::Ok); + } + delayUs(1); + } + SDIO->ICR=0x7ff;//Clear flags + return CmdResult(cc,CmdResult::Timeout); + } + + //Command is not CMD0, so wait a reply + for(int i=0;i<500;i++) + { + unsigned int status=SDIO->STA; + if(status & SDIO_STA_CMDREND) + { + SDIO->ICR=0x7ff;//Clear flags + if(SDIO->RESPCMD==cc) return CmdResult(cc,CmdResult::Ok); + else return CmdResult(cc,CmdResult::RespNotMatch); + } + if(status & SDIO_STA_CCRCFAIL) + { + SDIO->ICR=SDIO_ICR_CCRCFAILC; + return CmdResult(cc,CmdResult::CRCFail); + } + if(status & SDIO_STA_CTIMEOUT) break; + delayUs(1); + } + SDIO->ICR=SDIO_ICR_CTIMEOUTC; + return CmdResult(cc,CmdResult::Timeout); +} + +unsigned short Command::rca=0; + +// +// Class ClockController +// + +/** + * \internal + * This class controls the clock speed of the SDIO peripheral. It originated + * from a previous version of this driver, where the SDIO was used in polled + * mode instead of DMA mode, but has been retained to improve the robustness + * of the driver. + */ +class ClockController +{ +public: + + /** + * \internal. Set a low clock speed of 400KHz or less, used for + * detecting SD/MMC cards. This function as a side effect enables 1bit bus + * width, and disables clock powersave, since it is not allowed by SD spec. + */ + static void setLowSpeedClock() + { + clockReductionAvailable=0; + // No hardware flow control, SDIO_CK generated on rising edge, 1bit bus + // width, no clock bypass, no powersave. + // Set low clock speed 400KHz + SDIO->CLKCR=CLOCK_400KHz | SDIO_CLKCR_CLKEN; + SDIO->DTIMER=240000; //Timeout 600ms expressed in SD_CK cycles + } + + /** + * \internal + * Automatically select the data speed. This routine selects the highest + * sustainable data transfer speed. This is done by binary search until + * the highest clock speed that causes no errors is found. + * This function as a side effect enables 4bit bus width, and clock + * powersave. + */ + static void calibrateClockSpeed(SDIODriver *sdio); + + /** + * \internal + * Since clock speed is set dynamically by binary search at runtime, a + * corner case might be that of a clock speed which results in unreliable + * data transfer, that sometimes succeeds, and sometimes fail. + * For maximum robustness, this function is provided to reduce the clock + * speed slightly in case a data transfer should fail after clock + * calibration. To avoid inadvertently considering other kind of issues as + * clock issues, this function can be called only MAX_ALLOWED_REDUCTIONS + * times after clock calibration, subsequent calls will fail. This will + * avoid other issues causing an ever decreasing clock speed. + * \return true on success, false on failure + */ + static bool reduceClockSpeed(); + + /** + * \internal + * Read and write operation do retry during normal use for robustness, but + * during clock claibration they must not retry for speed reasons. This + * member function returns 1 during clock claibration and MAX_RETRY during + * normal use. + */ + static unsigned char getRetryCount() { return retries; } + +private: + /** + * Set SDIO clock speed + * \param clkdiv speed is SDIOCLK/(clkdiv+2) + */ + static void setClockSpeed(unsigned int clkdiv); + + static const unsigned int SDIOCLK=48000000; //On stm32f2 SDIOCLK is always 48MHz + static const unsigned int CLOCK_400KHz=118; //48MHz/(118+2)=400KHz + #ifdef OVERRIDE_SD_CLOCK_DIVIDER_MAX + //Some boards using SDRAM cause SDIO TX Underrun occasionally + static const unsigned int CLOCK_MAX=OVERRIDE_SD_CLOCK_DIVIDER_MAX; + #else //OVERRIDE_SD_CLOCK_DIVIDER_MAX + static const unsigned int CLOCK_MAX=0; //48MHz/(0+2) =24MHz + #endif //OVERRIDE_SD_CLOCK_DIVIDER_MAX + + #ifdef SD_ONE_BIT_DATABUS + ///\internal Clock enabled, bus width 1bit, clock powersave enabled. + static const unsigned int CLKCR_FLAGS=SDIO_CLKCR_CLKEN | SDIO_CLKCR_PWRSAV; + #else //SD_ONE_BIT_DATABUS + ///\internal Clock enabled, bus width 4bit, clock powersave enabled. + static const unsigned int CLKCR_FLAGS=SDIO_CLKCR_CLKEN | + SDIO_CLKCR_WIDBUS_0 | SDIO_CLKCR_PWRSAV; + #endif //SD_ONE_BIT_DATABUS + + ///\internal Maximum number of calls to IRQreduceClockSpeed() allowed + static const unsigned char MAX_ALLOWED_REDUCTIONS=1; + + ///\internal value returned by getRetryCount() while *not* calibrating clock. + static const unsigned char MAX_RETRY=10; + + ///\internal Used to allow only one call to reduceClockSpeed() + static unsigned char clockReductionAvailable; + + ///\internal value returned by getRetryCount() + static unsigned char retries; +}; + +void ClockController::calibrateClockSpeed(SDIODriver *sdio) +{ + //During calibration we call readBlock() which will call reduceClockSpeed() + //so not to invalidate calibration clock reduction must not be available + clockReductionAvailable=0; + retries=1; + + DBG("Automatic speed calibration\n"); + unsigned int buffer[512/sizeof(unsigned int)]; + unsigned int minFreq=CLOCK_400KHz; + unsigned int maxFreq=CLOCK_MAX; + unsigned int selected; + while(minFreq-maxFreq>1) + { + selected=(minFreq+maxFreq)/2; + DBG("Trying CLKCR=%d\n",selected); + setClockSpeed(selected); + if(sdio->readBlock(reinterpret_cast(buffer),512,0)==512) + minFreq=selected; + else maxFreq=selected; + } + //Last round of algorithm + setClockSpeed(maxFreq); + if(sdio->readBlock(reinterpret_cast(buffer),512,0)==512) + { + DBG("Optimal CLKCR=%d\n",maxFreq); + } else { + setClockSpeed(minFreq); + DBG("Optimal CLKCR=%d\n",minFreq); + } + + //Make clock reduction available + clockReductionAvailable=MAX_ALLOWED_REDUCTIONS; + retries=MAX_RETRY; +} + +bool ClockController::reduceClockSpeed() +{ + DBGERR("clock speed reduction requested\n"); + //Ensure this function can be called only a few times + if(clockReductionAvailable==0) return false; + clockReductionAvailable--; + + unsigned int currentClkcr=SDIO->CLKCR & 0xff; + if(currentClkcr==CLOCK_400KHz) return false; //No lower than this value + + //If the value of clockcr is low, increasing it by one is enough since + //frequency changes a lot, otherwise increase by 2. + if(currentClkcr<10) currentClkcr++; + else currentClkcr+=2; + + setClockSpeed(currentClkcr); + return true; +} + +void ClockController::setClockSpeed(unsigned int clkdiv) +{ + SDIO->CLKCR=clkdiv | CLKCR_FLAGS; + //Timeout 600ms expressed in SD_CK cycles + SDIO->DTIMER=(6*SDIOCLK)/((clkdiv+2)*10); +} + +unsigned char ClockController::clockReductionAvailable=false; +unsigned char ClockController::retries=ClockController::MAX_RETRY; + +// +// Data send/receive functions +// + +/** + * \internal + * Wait until the card is ready for data transfer. + * Can be called independently of the card being selected. + * \return true on success, false on failure + */ +static bool waitForCardReady() +{ + const int timeout=1500; //Timeout 1.5 second + const int sleepTime=2; + for(int i=0;iICR=0x7ff; + DMA2->LIFCR=DMA_LIFCR_CTCIF3 | + DMA_LIFCR_CTEIF3 | + DMA_LIFCR_CDMEIF3 | + DMA_LIFCR_CFEIF3; + + transferError=false; + dmaFlags=sdioFlags=0; + waiting=Thread::getCurrentThread(); + + //Select DMA transfer size based on buffer alignment. Best performance + //is achieved when the buffer is aligned on a 4 byte boundary + switch(reinterpret_cast(buffer) & 0x3) + { + case 0: return DMA_SxCR_MSIZE_1; //DMA reads 32bit at a time + case 2: return DMA_SxCR_MSIZE_0; //DMA reads 16bit at a time + default: return 0; //DMA reads 8bit at a time + } +} + +/** + * \internal + * Read a given number of contiguous 512 byte blocks from an SD/MMC card. + * Card must be selected prior to calling this function. + * \param buffer, a buffer whose size is 512*nblk bytes + * \param nblk number of blocks to read. + * \param lba logical block address of the first block to read. + */ +static bool multipleBlockRead(unsigned char *buffer, unsigned int nblk, + unsigned int lba) +{ + if(nblk==0) return true; + while(nblk>32767) + { + if(multipleBlockRead(buffer,32767,lba)==false) return false; + buffer+=32767*512; + nblk-=32767; + lba+=32767; + } + if(waitForCardReady()==false) return false; + + if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC + + unsigned int memoryTransferSize=dmaTransferCommonSetup(buffer); + + //Data transfer is considered complete once the DMA transfer complete + //interrupt occurs, that happens when the last data was written in the + //buffer. Both SDIO and DMA error interrupts are active to catch errors + SDIO->MASK=SDIO_MASK_STBITERRIE | //Interrupt on start bit error + SDIO_MASK_RXOVERRIE | //Interrupt on rx underrun + SDIO_MASK_TXUNDERRIE | //Interrupt on tx underrun + SDIO_MASK_DCRCFAILIE | //Interrupt on data CRC fail + SDIO_MASK_DTIMEOUTIE; //Interrupt on data timeout + DMA2_Stream3->PAR=reinterpret_cast(&SDIO->FIFO); + DMA2_Stream3->M0AR=reinterpret_cast(buffer); + //Note: DMA2_Stream3->NDTR is don't care in peripheral flow control mode + DMA2_Stream3->FCR=DMA_SxFCR_FEIE | //Interrupt on fifo error + DMA_SxFCR_DMDIS | //Fifo enabled + DMA_SxFCR_FTH_0; //Take action if fifo half full + DMA2_Stream3->CR=DMA_SxCR_CHSEL_2 | //Channel 4 (SDIO) + DMA_SxCR_PBURST_0 | //4-beat bursts read from SDIO + DMA_SxCR_PL_0 | //Medium priority DMA stream + memoryTransferSize | //RAM data size depends on alignment + DMA_SxCR_PSIZE_1 | //Read 32bit at a time from SDIO + DMA_SxCR_MINC | //Increment RAM pointer + 0 | //Peripheral to memory direction + DMA_SxCR_PFCTRL | //Peripheral is flow controller + DMA_SxCR_TCIE | //Interrupt on transfer complete + DMA_SxCR_TEIE | //Interrupt on transfer error + DMA_SxCR_DMEIE | //Interrupt on direct mode error + DMA_SxCR_EN; //Start the DMA + + SDIO->DLEN=nblk*512; + if(waiting==0) + { + DBGERR("Premature wakeup\n"); + transferError=true; + } + CmdResult cr=Command::send(nblk>1 ? Command::CMD18 : Command::CMD17,lba); + if(cr.validateR1Response()) + { + //Block size 512 bytes, block data xfer, from card to controller + SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTDIR | SDIO_DCTRL_DTEN; + FastInterruptDisableLock dLock; + while(waiting) + { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } else transferError=true; + DMA2_Stream3->CR=0; + while(DMA2_Stream3->CR & DMA_SxCR_EN) ; //DMA may take time to stop + SDIO->DCTRL=0; //Disable data path state machine + SDIO->MASK=0; + + // CMD12 is sent to end CMD18 (multiple block read), or to abort an + // unfinished read in case of errors + if(nblk>1 || transferError) cr=Command::send(Command::CMD12,0); + if(transferError || cr.validateR1Response()==false) + { + displayBlockTransferError(); + ClockController::reduceClockSpeed(); + return false; + } + + //Read ok, deal with cache coherence + markBufferAfterDmaRead(buffer,nblk*512); + return true; +} + +/** + * \internal + * Write a given number of contiguous 512 byte blocks to an SD/MMC card. + * Card must be selected prior to calling this function. + * \param buffer, a buffer whose size is 512*nblk bytes + * \param nblk number of blocks to write. + * \param lba logical block address of the first block to write. + */ +static bool multipleBlockWrite(const unsigned char *buffer, unsigned int nblk, + unsigned int lba) +{ + if(nblk==0) return true; + while(nblk>32767) + { + if(multipleBlockWrite(buffer,32767,lba)==false) return false; + buffer+=32767*512; + nblk-=32767; + lba+=32767; + } + + //Deal with cache coherence + markBufferBeforeDmaWrite(buffer,nblk*512); + + if(waitForCardReady()==false) return false; + + if(cardType!=SDHC) lba*=512; // Convert to byte address if not SDHC + if(nblk>1) + { + CmdResult cr=Command::send(Command::ACMD23,nblk); + if(cr.validateR1Response()==false) return false; + } + + unsigned int memoryTransferSize=dmaTransferCommonSetup(buffer); + + //Data transfer is considered complete once the SDIO transfer complete + //interrupt occurs, that happens when the last data was written to the SDIO + //Both SDIO and DMA error interrupts are active to catch errors + SDIO->MASK=SDIO_MASK_DATAENDIE | //Interrupt on data end + SDIO_MASK_STBITERRIE | //Interrupt on start bit error + SDIO_MASK_RXOVERRIE | //Interrupt on rx underrun + SDIO_MASK_TXUNDERRIE | //Interrupt on tx underrun + SDIO_MASK_DCRCFAILIE | //Interrupt on data CRC fail + SDIO_MASK_DTIMEOUTIE; //Interrupt on data timeout + DMA2_Stream3->PAR=reinterpret_cast(&SDIO->FIFO); + DMA2_Stream3->M0AR=reinterpret_cast(buffer); + //Note: DMA2_Stream3->NDTR is don't care in peripheral flow control mode + //Quirk: not enabling DMA_SxFCR_FEIE because the SDIO seems to generate + //a spurious fifo error. The code was tested and the transfer completes + //successfully even in the presence of this fifo error + DMA2_Stream3->FCR=DMA_SxFCR_DMDIS | //Fifo enabled + DMA_SxFCR_FTH_1 | //Take action if fifo full + DMA_SxFCR_FTH_0; + DMA2_Stream3->CR=DMA_SxCR_CHSEL_2 | //Channel 4 (SDIO) + DMA_SxCR_PBURST_0 | //4-beat bursts write to SDIO + DMA_SxCR_PL_0 | //Medium priority DMA stream + memoryTransferSize | //RAM data size depends on alignment + DMA_SxCR_PSIZE_1 | //Write 32bit at a time to SDIO + DMA_SxCR_MINC | //Increment RAM pointer + DMA_SxCR_DIR_0 | //Memory to peripheral direction + DMA_SxCR_PFCTRL | //Peripheral is flow controller + DMA_SxCR_TEIE | //Interrupt on transfer error + DMA_SxCR_DMEIE | //Interrupt on direct mode error + DMA_SxCR_EN; //Start the DMA + + SDIO->DLEN=nblk*512; + if(waiting==0) + { + DBGERR("Premature wakeup\n"); + transferError=true; + } + CmdResult cr=Command::send(nblk>1 ? Command::CMD25 : Command::CMD24,lba); + if(cr.validateR1Response()) + { + //Block size 512 bytes, block data xfer, from card to controller + SDIO->DCTRL=(9<<4) | SDIO_DCTRL_DMAEN | SDIO_DCTRL_DTEN; + FastInterruptDisableLock dLock; + while(waiting) + { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } else transferError=true; + DMA2_Stream3->CR=0; + while(DMA2_Stream3->CR & DMA_SxCR_EN) ; //DMA may take time to stop + SDIO->DCTRL=0; //Disable data path state machine + SDIO->MASK=0; + + // CMD12 is sent to end CMD25 (multiple block write), or to abort an + // unfinished write in case of errors + if(nblk>1 || transferError) cr=Command::send(Command::CMD12,0); + if(transferError || cr.validateR1Response()==false) + { + displayBlockTransferError(); + ClockController::reduceClockSpeed(); + return false; + } + return true; +} + +// +// Class CardSelector +// + +/** + * \internal + * Simple RAII class for selecting an SD/MMC card an automatically deselect it + * at the end of the scope. + */ +class CardSelector +{ +public: + /** + * \internal + * Constructor. Selects the card. + * The result of the select operation is available through its succeded() + * member function + */ + explicit CardSelector() + { + success=Command::send( + Command::CMD7,Command::getRca()<<16).validateR1Response(); + } + + /** + * \internal + * \return true if the card was selected, false on error + */ + bool succeded() { return success; } + + /** + * \internal + * Destructor, ensures that the card is deselected + */ + ~CardSelector() + { + Command::send(Command::CMD7,0); //Deselect card. This will timeout + } + +private: + bool success; +}; + +// +// Initialization helper functions +// + +/** + * \internal + * Initialzes the SDIO peripheral in the STM32 + */ +static void initSDIOPeripheral() +{ + { + //Doing read-modify-write on RCC->APBENR2 and gpios, better be safe + FastInterruptDisableLock lock; + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN + | RCC_AHB1ENR_GPIODEN + | RCC_AHB1ENR_DMA2EN; + RCC_SYNC(); + RCC->APB2ENR |= RCC_APB2ENR_SDIOEN; + RCC_SYNC(); + sdD0::mode(Mode::ALTERNATE); + sdD0::alternateFunction(12); + #ifndef SD_ONE_BIT_DATABUS + sdD1::mode(Mode::ALTERNATE); + sdD1::alternateFunction(12); + sdD2::mode(Mode::ALTERNATE); + sdD2::alternateFunction(12); + sdD3::mode(Mode::ALTERNATE); + sdD3::alternateFunction(12); + #endif //SD_ONE_BIT_DATABUS + sdCLK::mode(Mode::ALTERNATE); + sdCLK::alternateFunction(12); + sdCMD::mode(Mode::ALTERNATE); + sdCMD::alternateFunction(12); + } + NVIC_SetPriority(DMA2_Stream3_IRQn,15);//Low priority for DMA + NVIC_EnableIRQ(DMA2_Stream3_IRQn); + NVIC_SetPriority(SDIO_IRQn,15);//Low priority for SDIO + NVIC_EnableIRQ(SDIO_IRQn); + + SDIO->POWER=0; //Power off state + delayUs(1); + SDIO->CLKCR=0; + SDIO->CMD=0; + SDIO->DCTRL=0; + SDIO->ICR=0xc007ff; + SDIO->POWER=SDIO_POWER_PWRCTRL_1 | SDIO_POWER_PWRCTRL_0; //Power on state + //This delay is particularly important: when setting the POWER register a + //glitch on the CMD pin happens. This glitch has a fast fall time and a slow + //rise time resembling an RC charge with a ~6us rise time. If the clock is + //started too soon, the card sees a clock pulse while CMD is low, and + //interprets it as a start bit. No, setting POWER to powerup does not + //eliminate the glitch. + delayUs(10); + ClockController::setLowSpeedClock(); +} + +/** + * \internal + * Detect if the card is an SDHC, SDv2, SDv1, MMC + * \return Type of card: (1<<0)=MMC (1<<1)=SDv1 (1<<2)=SDv2 (1<<2)|(1<<3)=SDHC + * or Invalid if card detect failed. + */ +static CardType detectCardType() +{ + const int INIT_TIMEOUT=200; //200*10ms= 2 seconds + CmdResult r=Command::send(Command::CMD8,0x1aa); + if(r.validateError()) + { + //We have an SDv2 card connected + if(r.getResponse()!=0x1aa) + { + DBGERR("CMD8 validation: voltage range fail\n"); + return Invalid; + } + for(int i=0;i SDIODriver::instance() +{ + static FastMutex m; + static intrusive_ref_ptr instance; + Lock l(m); + if(!instance) instance=new SDIODriver(); + return instance; +} + +ssize_t SDIODriver::readBlock(void* buffer, size_t size, off_t where) +{ + if(where % 512 || size % 512) return -EFAULT; + unsigned int lba=where/512; + unsigned int nSectors=size/512; + Lock l(mutex); + DBG("SDIODriver::readBlock(): nSectors=%d\n",nSectors); + bool goodBuffer=BufferConverter::isGoodBuffer(buffer); + if(goodBuffer==false) DBG("Buffer inside CCM\n"); + + for(int i=0;i(buffer), + nSectors,lba)==false) error=true; + } else { + //Fallback code to work around CCM + unsigned char *tempBuffer=reinterpret_cast(buffer); + unsigned int tempLba=lba; + for(unsigned int j=0;j0) DBGERR("Read: required %d retries\n",i); + return size; + } + } + return -EBADF; +} + +ssize_t SDIODriver::writeBlock(const void* buffer, size_t size, off_t where) +{ + if(where % 512 || size % 512) return -EFAULT; + unsigned int lba=where/512; + unsigned int nSectors=size/512; + Lock l(mutex); + DBG("SDIODriver::writeBlock(): nSectors=%d\n",nSectors); + bool goodBuffer=BufferConverter::isGoodBuffer(buffer); + if(goodBuffer==false) DBG("Buffer inside CCM\n"); + + for(int i=0;i(buffer), + nSectors,lba)==false) error=true; + } else { + //Fallback code to work around CCM + const unsigned char *tempBuffer= + reinterpret_cast(buffer); + unsigned int tempLba=lba; + for(unsigned int j=0;j0) DBGERR("Write: required %d retries\n",i); + return size; + } + } + return -EBADF; +} + +int SDIODriver::ioctl(int cmd, void* arg) +{ + DBG("SDIODriver::ioctl()\n"); + if(cmd!=IOCTL_SYNC) return -ENOTTY; + Lock l(mutex); + //Note: no need to select card, since status can be queried even with card + //not selected. + return waitForCardReady() ? 0 : -EFAULT; +} + +SDIODriver::SDIODriver() : Device(Device::BLOCK) +{ + initSDIOPeripheral(); + + // This is more important than it seems, since CMD55 requires the card's RCA + // as argument. During initalization, after CMD0 the card has an RCA of zero + // so without this line ACMD41 will fail and the card won't be initialized. + Command::setRca(0); + + //Send card reset command + CmdResult r=Command::send(Command::CMD0,0); + if(r.validateError()==false) return; + + cardType=detectCardType(); + if(cardType==Invalid) return; //Card detect failed + if(cardType==MMC) return; //MMC cards currently unsupported + + // Now give an RCA to the card. In theory we should loop and enumerate all + // the cards but this driver supports only one card. + r=Command::send(Command::CMD2,0); + //CMD2 sends R2 response, whose CMDINDEX field is wrong + if(r.getError()!=CmdResult::Ok && r.getError()!=CmdResult::RespNotMatch) + { + r.validateError(); + return; + } + r=Command::send(Command::CMD3,0); + if(r.validateR6Response()==false) return; + Command::setRca(r.getResponse()>>16); + DBG("Got RCA=%u\n",Command::getRca()); + if(Command::getRca()==0) + { + //RCA=0 can't be accepted, since it is used to deselect cards + DBGERR("RCA=0 is invalid\n"); + return; + } + + //Lastly, try selecting the card and configure the latest bits + { + CardSelector selector; + if(selector.succeded()==false) return; + + r=Command::send(Command::CMD13,Command::getRca()<<16);//Get status + if(r.validateR1Response()==false) return; + if(r.getState()!=4) //4=Tran state + { + DBGERR("CMD7 was not able to select card\n"); + return; + } + + #ifndef SD_ONE_BIT_DATABUS + r=Command::send(Command::ACMD6,2); //Set 4 bit bus width + if(r.validateR1Response()==false) return; + #endif //SD_ONE_BIT_DATABUS + + if(cardType!=SDHC) + { + r=Command::send(Command::CMD16,512); //Set 512Byte block length + if(r.validateR1Response()==false) return; + } + } + + // Now that card is initialized, perform self calibration of maximum + // possible read/write speed. This as a side effect enables 4bit bus width. + ClockController::calibrateClockSpeed(this); + + DBG("SDIO init: Success\n"); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f2_f4.h b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f2_f4.h new file mode 100644 index 00000000..49216dcc --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/sd_stm32f2_f4.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SD_STM32F2_F4_H +#define SD_STM32F2_F4_H + +#include "kernel/sync.h" +#include "filesystem/devfs/devfs.h" +#include "filesystem/ioctl.h" + +namespace miosix { + +/** + * Driver for the SDIO peripheral in STM32F2 and F4 microcontrollers + */ +class SDIODriver : public Device +{ +public: + /** + * \return an instance to this class, singleton + */ + static intrusive_ref_ptr instance(); + + virtual ssize_t readBlock(void *buffer, size_t size, off_t where); + + virtual ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + virtual int ioctl(int cmd, void *arg); +private: + /** + * Constructor + */ + SDIODriver(); + + FastMutex mutex; +}; + +} //namespace miosix + +#endif //SD_STM32F2_F4_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial.h b/lib/miosix-kernel/miosix/arch/common/drivers/serial.h new file mode 100644 index 00000000..1ccb3d3b --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial.h @@ -0,0 +1,18 @@ + +//Serial code is common for all the cortex M cores, so it has been put here + +#ifdef _ARCH_ARM7_LPC2000 +#include "serial_lpc2000.h" +#elif defined(_ARCH_CORTEXM0_STM32) || defined(_ARCH_CORTEXM3_STM32) \ + || defined(_ARCH_CORTEXM4_STM32F4) || defined(_ARCH_CORTEXM3_STM32F2) \ + || defined(_ARCH_CORTEXM3_STM32L1) || defined(_ARCH_CORTEXM7_STM32F7) \ + || defined(_ARCH_CORTEXM7_STM32H7) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) +#include "serial_stm32.h" +#elif defined(_ARCH_CORTEXM3_EFM32GG) +#include "serial_efm32.h" +#elif defined(_ARCH_CORTEXM4_ATSAM4L) +#include "serial_atsam4l.h" +#else +#error "Unknown arch" +#endif diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_atsam4l.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/serial_atsam4l.cpp new file mode 100644 index 00000000..a62b515b --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_atsam4l.cpp @@ -0,0 +1,245 @@ +/*************************************************************************** + * Copyright (C) 2020 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include +#include "serial_atsam4l.h" +#include "kernel/sync.h" +#include "kernel/scheduler/scheduler.h" +#include "interfaces/portability.h" +#include "filesystem/ioctl.h" + +using namespace std; +using namespace miosix; + +static const int numPorts=4; //USART0 to USART3 + +/// Pointer to serial port classes to let interrupts access the classes +static ATSAMSerial *ports[numPorts]={0}; + +/** + * \internal interrupt routine for usart2 rx actual implementation + */ +void __attribute__((noinline)) usart2rxIrqImpl() +{ + if(ports[2]) ports[2]->IRQhandleInterrupt(); +} + +/** + * \internal interrupt routine for usart2 rx + */ +void __attribute__((naked)) USART2_Handler() +{ + saveContext(); + asm volatile("bl _Z15usart2rxIrqImplv"); + restoreContext(); +} + +namespace miosix { + +// +// class ATSAMSerial +// + +// A note on the baudrate/500: the buffer is selected so as to withstand +// 20ms of full data rate. In the 8N1 format one char is made of 10 bits. +// So (baudrate/10)*0.02=baudrate/500 +ATSAMSerial::ATSAMSerial(int id, int baudrate) + : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500), rxWaiting(0), + idle(true), portId(id) +{ + if(id!=2 || ports[portId]) errorHandler(UNEXPECTED); + + { + InterruptDisableLock dLock; + ports[portId]=this; + + port=USART2; + + //TODO: USART2 hardcoded + PM->PM_UNLOCK=0xaa<<24 | PM_PBAMASK_OFFSET; + PM->PM_PBAMASK |= PM_PBAMASK_USART2; + NVIC_SetPriority(USART2_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(USART2_IRQn); + } + + port->US_BRGR = ((SystemCoreClock/baudrate/4)+1)/2; //TODO: fractional part + port->US_MR = US_MR_FILTER // Filter input with majority of 3 samples + | US_MR_OVER // 8 cycles oversample + | US_MR_PAR_NONE // No parity + | US_MR_CHRL_8 // 8 bit char + | US_MR_USCLKS_MCK // CLK_USART is clock source + | US_MR_MODE_NORMAL;// Just a plain usart, please + + port->US_RTOR=10; //Timeout 10 bits (one char time) + port->US_IER = US_IER_RXRDY | US_IER_TIMEOUT; + + port->US_CR = US_CR_TXEN | US_CR_RXEN; +} + +ssize_t ATSAMSerial::readBlock(void *buffer, size_t size, off_t where) +{ + Lock l(rxMutex); + char *buf=reinterpret_cast(buffer); + size_t result=0; + FastInterruptDisableLock dLock; + for(;;) + { + //Try to get data from the queue + for(;result0) break; + if(result==size) break; + //Wait for data in the queue + do { + rxWaiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(rxWaiting); + } + return result; +} + +ssize_t ATSAMSerial::writeBlock(const void *buffer, size_t size, off_t where) +{ + Lock l(txMutex); + const char *buf=reinterpret_cast(buffer); + for(size_t i=0;iUS_CSR & US_CSR_TXRDY) == 0) ; + port->US_THR =*buf++; + } + return size; +} + +void ATSAMSerial::IRQwrite(const char *str) +{ + // We can reach here also with only kernel paused, so make sure + // interrupts are disabled. + bool interrupts=areInterruptsEnabled(); + if(interrupts) fastDisableInterrupts(); + while(*str) + { + while((port->US_CSR & US_CSR_TXRDY) == 0) ; + port->US_THR = *str++; + } + waitSerialTxFifoEmpty(); + if(interrupts) fastEnableInterrupts(); +} + +int ATSAMSerial::ioctl(int cmd, void *arg) +{ + if(reinterpret_cast(arg) & 0b11) return -EFAULT; //Unaligned + termios *t=reinterpret_cast(arg); + switch(cmd) + { + case IOCTL_SYNC: + waitSerialTxFifoEmpty(); + return 0; + case IOCTL_TCGETATTR: + t->c_iflag=IGNBRK | IGNPAR; + t->c_oflag=0; + t->c_cflag=CS8; + t->c_lflag=0; + return 0; + case IOCTL_TCSETATTR_NOW: + case IOCTL_TCSETATTR_DRAIN: + case IOCTL_TCSETATTR_FLUSH: + //Changing things at runtime unsupported, so do nothing, but don't + //return error as console_device.h implements some attribute changes + return 0; + default: + return -ENOTTY; //Means the operation does not apply to this descriptor + } +} + +void ATSAMSerial::IRQhandleInterrupt() +{ + bool wake=false; + unsigned int status=port->US_CSR; + + if(status & US_CSR_RXRDY) + { + wake=true; + //Always read the char (resets flags), but put it in the queue only if + //no framing error + char c=port->US_RHR; + if((status & US_CSR_FRAME) == 0) + { + if(rxQueue.tryPut(c & 0xff)==false) /*fifo overflow*/; + idle=false; + } + } + if(status & US_CSR_TIMEOUT) + { + wake=true; + port->US_CR=US_CR_STTTO; + idle=true; + } + + if(wake && rxWaiting) + { + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + rxWaiting=0; + } +} + +ATSAMSerial::~ATSAMSerial() +{ + waitSerialTxFifoEmpty(); + + port->US_CR = US_CR_TXDIS | US_CR_RXDIS; + port->US_IDR = US_IDR_RXRDY | US_IDR_TIMEOUT; + + InterruptDisableLock dLock; + ports[portId]=nullptr; + + //TODO: USART2 hardcoded + NVIC_DisableIRQ(USART2_IRQn); + NVIC_ClearPendingIRQ(USART2_IRQn); + PM->PM_UNLOCK=0xaa<<24 | PM_PBAMASK_OFFSET; + PM->PM_PBAMASK &= ~PM_PBAMASK_USART2; +} + +void ATSAMSerial::waitSerialTxFifoEmpty() +{ + while((port->US_CSR & US_CSR_TXEMPTY) == 0) ; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_atsam4l.h b/lib/miosix-kernel/miosix/arch/common/drivers/serial_atsam4l.h new file mode 100644 index 00000000..620a7077 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_atsam4l.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * Copyright (C) 2020 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#pragma once + +#include "filesystem/console/console_device.h" +#include "kernel/sync.h" +#include "kernel/queue.h" +#include "board_settings.h" + +namespace miosix { + +/** + * Serial port class for ATSAM4L microcontrollers. + * + * This is a simple implementation with the following limitations: + * -only supports USART2 + * -no DMA, TX uses polling, while RX is interrupt-based + * + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + */ +class ATSAMSerial : public Device +{ +public: + /** + * Constructor, initializes the serial port. + * Calls errorHandler(UNEXPECTED) if id is not in the correct range, or when + * attempting to construct multiple objects with the same id. That is, + * it is possible to instantiate only one instance of this class for each + * hardware USART. + * \param id a number to select the USART + * \param baudrate serial port baudrate + */ + ATSAMSerial(int id, int baudrate); + + /** + * Read a block of data + * \param buffer buffer where read data will be stored + * \param size buffer size + * \param where where to read from + * \return number of bytes read or a negative number on failure. Note that + * it is normal for this function to return less character than the amount + * asked + */ + ssize_t readBlock(void *buffer, size_t size, off_t where); + + /** + * Write a block of data + * \param buffer buffer where take data to write + * \param size buffer size + * \param where where to write to + * \return number of bytes written or a negative number on failure + */ + ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + /** + * Write a string. + * An extension to the Device interface that adds a new member function, + * which is used by the kernel on console devices to write debug information + * before the kernel is started or in case of serious errors, right before + * rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * \param str the string to write. The string must be NUL terminated. + */ + void IRQwrite(const char *str); + + /** + * Performs device-specific operations + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int ioctl(int cmd, void *arg); + + /** + * \internal the serial port interrupts call this member function. + * Never call this from user code. + */ + void IRQhandleInterrupt(); + + /** + * \return port id, 0 for USART0, ... + */ + int getId() const { return portId; } + + /** + * Destructor + */ + ~ATSAMSerial(); + +private: + /** + * Wait until all characters have been written to the serial port + */ + void waitSerialTxFifoEmpty(); + + FastMutex txMutex; ///< Mutex locked during transmission + FastMutex rxMutex; ///< Mutex locked during reception + + DynUnsyncQueue rxQueue; ///< Receiving queue + static const unsigned int rxQueueMin=1; ///< Minimum queue size + Thread *rxWaiting; ///< Thread waiting for rx, or 0 + bool idle; ///< Receiver idle + + Usart *port; ///< Pointer to USART peripheral + + const unsigned char portId; ///< 0 for USART0, ... +}; + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_efm32.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/serial_efm32.cpp new file mode 100644 index 00000000..7ce27ba0 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_efm32.cpp @@ -0,0 +1,279 @@ +/*************************************************************************** + * Copyright (C) 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include +#include "serial_efm32.h" +#include "kernel/sync.h" +#include "kernel/scheduler/scheduler.h" +#include "interfaces/portability.h" +#include "interfaces/gpio.h" +#include "filesystem/ioctl.h" + +using namespace std; +using namespace miosix; + +static const int numPorts=1; //Supporting only USART0 for now + +//Hope GPIO mapping doesn't change among EFM32 microcontrollers, otherwise +//we'll fix that with #ifdefs +typedef Gpio u0tx; +typedef Gpio u0rx; + +/// Pointer to serial port classes to let interrupts access the classes +static EFM32Serial *ports[numPorts]={0}; + +/** + * \internal interrupt routine for usart0 rx actual implementation + */ +void __attribute__((noinline)) usart0rxIrqImpl() +{ + if(ports[0]) ports[0]->IRQhandleInterrupt(); +} + +/** + * \internal interrupt routine for usart0 rx + */ +void __attribute__((naked)) USART0_RX_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart0rxIrqImplv"); + restoreContext(); +} + +namespace miosix { + +// +// class EFM32Serial +// + +// A note on the baudrate/500: the buffer is selected so as to withstand +// 20ms of full data rate. In the 8N1 format one char is made of 10 bits. +// So (baudrate/10)*0.02=baudrate/500 +EFM32Serial::EFM32Serial(int id, int baudrate) + : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500), rxWaiting(0), + portId(id), baudrate(baudrate) +{ + if(id<0 || id>=numPorts || ports[id]!=0) errorHandler(UNEXPECTED); + + { + InterruptDisableLock dLock; + ports[id]=this; + + switch(id) + { + case 0: + u0tx::mode(Mode::OUTPUT_HIGH); + u0rx::mode(Mode::INPUT_PULL_UP_FILTER); + + CMU->HFPERCLKEN0|=CMU_HFPERCLKEN0_USART0; + port=USART0; + + port->IEN=USART_IEN_RXDATAV; + port->IRCTRL=0; //USART0 also has IrDA mode + + NVIC_SetPriority(USART0_RX_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(USART0_RX_IRQn); + break; + } + } + + port->CTRL=USART_CTRL_TXBIL_HALFFULL; //Use the buffer more efficiently + port->FRAME=USART_FRAME_STOPBITS_ONE + | USART_FRAME_PARITY_NONE + | USART_FRAME_DATABITS_EIGHT; + port->TRIGCTRL=0; + port->INPUT=0; + port->I2SCTRL=0; + port->ROUTE=USART_ROUTE_LOCATION_LOC0 //Default location + | USART_ROUTE_TXPEN //Enable TX pin + | USART_ROUTE_RXPEN; //Enable RX pin + unsigned int periphClock=SystemHFClockGet()/(1<<(CMU->HFPERCLKDIV & 0xf)); + //The number we need is periphClock/baudrate/16-1, but with two bits of + //fractional part. We divide by 2 instead of 16 to have 3 bit of fractional + //part. We use the additional fractional bit to add one to round towards + //the nearest. This gets us a little more precision. Then we subtract 8 + //which is one with three fractional bits. Then we shift to fit the integer + //part in bits 20:8 and the fractional part in bits 7:6, masking away the + //third fractional bit. Easy, isn't it? Not quite. + port->CLKDIV=((((periphClock/baudrate/2)+1)-8)<<5) & 0x1fffc0; + port->CMD=USART_CMD_CLEARRX + | USART_CMD_CLEARTX + | USART_CMD_TXTRIDIS + | USART_CMD_RXBLOCKDIS + | USART_CMD_MASTEREN + | USART_CMD_TXEN + | USART_CMD_RXEN; +} + +ssize_t EFM32Serial::readBlock(void *buffer, size_t size, off_t where) +{ + Lock l(rxMutex); + char *buf=reinterpret_cast(buffer); + size_t result=0; + FastInterruptDisableLock dLock; + for(;;) + { + //Try to get data from the queue + for(;result0) break; + //Wait for data in the queue + do { + rxWaiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(rxWaiting); + } + return result; +} + +ssize_t EFM32Serial::writeBlock(const void *buffer, size_t size, off_t where) +{ + Lock l(txMutex); + const char *buf=reinterpret_cast(buffer); + for(size_t i=0;iSTATUS & USART_STATUS_TXBL)==0) ; + port->TXDATA=*buf++; + } + return size; +} + +void EFM32Serial::IRQwrite(const char *str) +{ + // We can reach here also with only kernel paused, so make sure + // interrupts are disabled. + bool interrupts=areInterruptsEnabled(); + if(interrupts) fastDisableInterrupts(); + while(*str) + { + while((port->STATUS & USART_STATUS_TXBL)==0) ; + port->TXDATA=*str++; + } + waitSerialTxFifoEmpty(); + if(interrupts) fastEnableInterrupts(); +} + +int EFM32Serial::ioctl(int cmd, void* arg) +{ + if(reinterpret_cast(arg) & 0b11) return -EFAULT; //Unaligned + termios *t=reinterpret_cast(arg); + switch(cmd) + { + case IOCTL_SYNC: + waitSerialTxFifoEmpty(); + return 0; + case IOCTL_TCGETATTR: + t->c_iflag=IGNBRK | IGNPAR; + t->c_oflag=0; + t->c_cflag=CS8; + t->c_lflag=0; + return 0; + case IOCTL_TCSETATTR_NOW: + case IOCTL_TCSETATTR_DRAIN: + case IOCTL_TCSETATTR_FLUSH: + //Changing things at runtime unsupported, so do nothing, but don't + //return error as console_device.h implements some attribute changes + return 0; + default: + return -ENOTTY; //Means the operation does not apply to this descriptor + } +} + +void EFM32Serial::IRQhandleInterrupt() +{ + bool atLeastOne=false; + while(port->STATUS & USART_STATUS_RXDATAV) + { + unsigned int c=port->RXDATAX; + if((c & (USART_RXDATAX_FERR | USART_RXDATAX_PERR))==0) + { + atLeastOne=true; + if(rxQueue.tryPut(c & 0xff)==false) /*fifo overflow*/; + } + } + if(atLeastOne && rxWaiting) + { + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + rxWaiting=0; + } + +} + +EFM32Serial::~EFM32Serial() +{ + waitSerialTxFifoEmpty(); + + port->CMD=USART_CMD_TXDIS + | USART_CMD_RXDIS; + port->ROUTE=0; + + InterruptDisableLock dLock; + ports[portId]=0; + switch(portId) + { + case 0: + NVIC_DisableIRQ(USART0_RX_IRQn); + NVIC_ClearPendingIRQ(USART0_RX_IRQn); + + u0tx::mode(Mode::DISABLED); + u0rx::mode(Mode::DISABLED); + + CMU->HFPERCLKEN0 &= ~CMU_HFPERCLKEN0_USART0; + break; + } +} + +void EFM32Serial::waitSerialTxFifoEmpty() +{ + //The documentation states that the TXC bit goes to one as soon as a + //transmission is complete. However, this bit is initially zero, so if we + //call this function before transmitting, the loop will wait forever. As a + //solution, add a timeout having as value the time needed to send three + //bytes (the current one in the shift register plus the two in the buffer). + //The +1 is to produce rounding on the safe side, the 30 is the time to send + //three char through the port, including start and stop bits. + int timeout=(SystemCoreClock/baudrate+1)*30; + while(timeout-->0 && (port->STATUS & USART_STATUS_TXC)==0) ; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_efm32.h b/lib/miosix-kernel/miosix/arch/common/drivers/serial_efm32.h new file mode 100644 index 00000000..05733eaf --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_efm32.h @@ -0,0 +1,139 @@ +/*************************************************************************** + * Copyright (C) 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SERIAL_EFM32_H +#define SERIAL_EFM32_H + +#include "filesystem/console/console_device.h" +#include "kernel/sync.h" +#include "kernel/queue.h" +#include "board_settings.h" + +namespace miosix { + +/** + * Serial port class for EFM32 microcontrollers. + * + * This is a simple implementation with the following limitations: + * -only supports USART0 + * -no DMA, TX uses polling, while RX is interrupt-based + * + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + */ +class EFM32Serial : public Device +{ +public: + /** + * Constructor, initializes the serial port. + * Calls errorHandler(UNEXPECTED) if id is not in the correct range, or when + * attempting to construct multiple objects with the same id. That is, + * it is possible to instantiate only one instance of this class for each + * hardware USART. + * \param id a number to select the USART + * \param baudrate serial port baudrate + */ + EFM32Serial(int id, int baudrate); + + /** + * Read a block of data + * \param buffer buffer where read data will be stored + * \param size buffer size + * \param where where to read from + * \return number of bytes read or a negative number on failure. Note that + * it is normal for this function to return less character than the amount + * asked + */ + ssize_t readBlock(void *buffer, size_t size, off_t where); + + /** + * Write a block of data + * \param buffer buffer where take data to write + * \param size buffer size + * \param where where to write to + * \return number of bytes written or a negative number on failure + */ + ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + /** + * Write a string. + * An extension to the Device interface that adds a new member function, + * which is used by the kernel on console devices to write debug information + * before the kernel is started or in case of serious errors, right before + * rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * \param str the string to write. The string must be NUL terminated. + */ + void IRQwrite(const char *str); + + /** + * Performs device-specific operations + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int ioctl(int cmd, void *arg); + + /** + * \internal the serial port interrupts call this member function. + * Never call this from user code. + */ + void IRQhandleInterrupt(); + + /** + * \return port id, 0 for USART0, ... + */ + int getId() const { return portId; } + + /** + * Destructor + */ + ~EFM32Serial(); + +private: + /** + * Wait until all characters have been written to the serial port + */ + void waitSerialTxFifoEmpty(); + + FastMutex txMutex; ///< Mutex locked during transmission + FastMutex rxMutex; ///< Mutex locked during reception + + DynUnsyncQueue rxQueue; ///< Receiving queue + static const unsigned int rxQueueMin=1; ///< Minimum queue size + Thread *rxWaiting; ///< Thread waiting for rx, or 0 + + USART_TypeDef *port; ///< Pointer to USART peripheral + + const unsigned char portId; ///< 0 for USART0, ... + int baudrate; ///< Baudrate +}; + +} //namespace miosix + +#endif //SERIAL_EFM32_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_lpc2000.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/serial_lpc2000.cpp new file mode 100644 index 00000000..cb0f3732 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_lpc2000.cpp @@ -0,0 +1,304 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 * + * by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include "serial_lpc2000.h" +#include "kernel/sync.h" +#include "kernel/scheduler/scheduler.h" +#include "interfaces/portability.h" +#include "filesystem/ioctl.h" +#include "LPC213x.h" + +namespace miosix { + +/// Pointer to serial port classes to let interrupts access the classes +static LPC2000Serial *ports[2]={0}; + +/** + * \internal interrupt routine for usart0 actual implementation + */ +void __attribute__((noinline)) usart0irqImpl() +{ + if(ports[0]) ports[0]->IRQhandleInterrupt(); + VICVectAddr=0xff;//Restart VIC +} + +/** + * \internal interrupt routine for usart0 + */ +void __attribute__((interrupt("IRQ"),naked)) usart0irq() +{ + saveContextFromIrq(); + asm volatile("bl _ZN6miosix13usart0irqImplEv"); + restoreContext(); +} + +/** + * \internal interrupt routine for usart1 actual implementation + */ +void __attribute__((noinline)) usart1irqImpl() +{ + if(ports[1]) ports[1]->IRQhandleInterrupt(); + VICVectAddr=0xff;//Restart VIC +} + +/** + * \internal interrupt routine for usart1 + */ +void __attribute__ ((interrupt("IRQ"),naked)) usart1irq() +{ + saveContextFromIrq(); + asm volatile("bl _ZN6miosix13usart1irqImplEv"); + restoreContext(); +} + +// +// class LPC2000Serial +// + +// A note on the baudrate/500: the buffer is selected so as to withstand +// 20ms of full data rate. In the 8N1 format one char is made of 10 bits. +// So (baudrate/10)*0.02=baudrate/500 +LPC2000Serial::LPC2000Serial(int id, int baudrate) : Device(Device::TTY), + rxQueue(hwRxQueueLen+baudrate/500), rxWaiting(0), idle(true) +{ + InterruptDisableLock dLock; + if(id<0 || id>1 || ports[id]!=0) errorHandler(UNEXPECTED); + ports[id]=this; + if(id==0) + { + serial=reinterpret_cast(0xe000c000); + PCONP|=(1<<3);//Enable UART0 peripheral + PINSEL0&=~(0xf);//Clear bits 0 to 3 of PINSEL0 + PINSEL0|=0x5;//Set p0.0 as TXD and p0.1 as RXD + //Init VIC + VICSoftIntClr=(1<<6);//Clear uart0 interrupt flag (if previously set) + VICIntSelect&=~(1<<6);//uart0=IRQ + VICIntEnable=(1<<6);//uart0 interrupt ON + VICVectCntl2=0x20 | 0x6;//Slot 2 of VIC used by uart0 + VICVectAddr2=reinterpret_cast(&usart0irq); + } else { + serial=reinterpret_cast(0xe0010000); + PCONP|=(1<<4);//Enable UART1 peripheral + PINSEL0&=~(0xf0000);//Clear bits 16 to 19 of PINSEL0 + PINSEL0|=0x50000;//Set p0.8 as TXD and p0.9 as RXD + //Init VIC + VICSoftIntClr=(1<<7);//Clear uart1 interrupt flag (if previously set) + VICIntSelect&=~(1<<7);//uart1=IRQ + VICIntEnable=(1<<7);//uart1 interrupt ON + VICVectCntl3=0x20 | 0x7;//Slot 3 of VIC used by uart1 + VICVectAddr3=reinterpret_cast(&usart1irq); + } + serial->LCR=0x3;//DLAB disabled + //0x07= fifo enabled, reset tx and rx hardware fifos + //0x80= uart rx fifo trigger level 8 characters + serial->FCR=0x07 | 0x80; + serial->LCR=0x83;//8data bit, 1stop, no parity, DLAB enabled + int div=TIMER_CLOCK/16/baudrate; + serial->DLL=div & 0xff; + serial->DLM=(div>>8) & 0xff; + serial->LCR=0x3;//DLAB disabled + serial->IER=0x7;//Enable RLS, RDA, CTI and THRE interrupts +} + +ssize_t LPC2000Serial::readBlock(void *buffer, size_t size, off_t where) +{ + Lock l(rxMutex); + char *buf=reinterpret_cast(buffer); + size_t result=0; + FastInterruptDisableLock dLock; + for(;;) + { + //Try to get data from the queue + for(;result0) break; + if(result==size) break; + //Wait for data in the queue + do { + rxWaiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(rxWaiting); + } + return result; +} + +ssize_t LPC2000Serial::writeBlock(const void *buffer, size_t size, off_t where) +{ + Lock l(txMutex); + FastInterruptDisableLock dLock; + size_t len=size; + const char *buf=reinterpret_cast(buffer); + while(len>0) + { + //If no data in software and hardware queue + if((serial->LSR & (1<<5)) && (txQueue.isEmpty())) + { + //Fill hardware queue first + for(int i=0;iTHR=*buf++; + len--; + if(len==0) break; + } + } else { + if(txQueue.IRQput(*buf)==true) + { + buf++; + len--; + } else { + FastInterruptEnableLock eLock(dLock); + txQueue.waitUntilNotFull(); + } + } + } + return size; +} + +void LPC2000Serial::IRQwrite(const char *str) +{ + while((*str)!='\0') + { + //Wait until the hardware fifo is ready to accept one char + while(!(serial->LSR & (1<<5))) ; //wait + serial->THR=*str++; + } + waitSerialTxFifoEmpty(); +} + +int LPC2000Serial::ioctl(int cmd, void* arg) +{ + if(reinterpret_cast(arg) & 0b11) return -EFAULT; //Unaligned + termios *t=reinterpret_cast(arg); + switch(cmd) + { + case IOCTL_SYNC: + waitSerialTxFifoEmpty(); + return 0; + case IOCTL_TCGETATTR: + t->c_iflag=IGNBRK | IGNPAR; + t->c_oflag=0; + t->c_cflag=CS8; + t->c_lflag=0; + return 0; + case IOCTL_TCSETATTR_NOW: + case IOCTL_TCSETATTR_DRAIN: + case IOCTL_TCSETATTR_FLUSH: + //Changing things at runtime unsupported, so do nothing, but don't + //return error as console_device.h implements some attribute changes + return 0; + default: + return -ENOTTY; //Means the operation does not apply to this descriptor + } +} + +void LPC2000Serial::IRQhandleInterrupt() +{ + char c; + bool hppw=false; + bool wakeup=false; + switch(serial->IIR & 0xf) + { + case 0x6: //RLS + c=serial->LSR;//Read LSR to clear interrupt + c=serial->RBR;//Read RBR to discard char that caused error + break; + case 0x4: //RDA + //Note: read one less char than HARDWARE_RX_QUEUE_LENGTH as the + //CTI interrupt only occurs if there's at least one character in + //the buffer, and we always want the CTI interrupt + for(int i=0;iRBR)==false) /*fifo overflow*/; + wakeup=true; + idle=false; + break; + case 0xc: //CTI + while(serial->LSR & (1<<0)) + if(rxQueue.tryPut(serial->RBR)==false) /*fifo overflow*/; + wakeup=true; + idle=true; + break; + case 0x2: //THRE + for(int i=0;iTHR=c; + } + break; + } + if(wakeup && rxWaiting) + { + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true; + rxWaiting=0; + } + if(hppw) Scheduler::IRQfindNextThread(); +} + +LPC2000Serial::~LPC2000Serial() +{ + waitSerialTxFifoEmpty(); + + InterruptDisableLock dLock; + //Disable UART0 + serial->LCR=0;//DLAB disabled + serial->FCR=0; + + int id=0; + if(ports[0]==this) id=0; + else if(ports[1]==this) id=1; + else errorHandler(UNEXPECTED); + ports[id]=0; + + if(id==0) + { + //Disable VIC + VICIntEnClr=(1<<6); + //Disable PIN + PINSEL0&=~(0xf);//Clear bits 0 to 3 of PINSEL0 + } else { + //Disable VIC + VICIntEnClr=(1<<7); + //Disable PIN + PINSEL0&=~(0xf0000);//Clear bits 16 to 19 of PINSEL0 + } +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_lpc2000.h b/lib/miosix-kernel/miosix/arch/common/drivers/serial_lpc2000.h new file mode 100644 index 00000000..67a41cf0 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_lpc2000.h @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 * + * by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SERIAL_LPC2000_H +#define SERIAL_LPC2000_H + +#include "filesystem/console/console_device.h" +#include "kernel/sync.h" +#include "kernel/queue.h" +#include "interfaces/delays.h" + +namespace miosix { + +/** + * Serial port class for LPC2000 microcontrollers. + * + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + */ +class LPC2000Serial : public Device +{ +public: + /** + * Constructor, initializes the serial port. + * Calls errorHandler(UNEXPECTED) if id is not in the correct range, or when + * attempting to construct multiple objects with the same id. That is, + * it is possible to instantiate only one instance of this class for each + * hardware USART. + * \param id 0=USART0, 1=USART1 + * \param baudrate serial port baudrate. + */ + LPC2000Serial(int id, int baudrate); + + /** + * Read a block of data + * \param buffer buffer where read data will be stored + * \param size buffer size + * \param where where to read from + * \return number of bytes read or a negative number on failure + */ + ssize_t readBlock(void *buffer, size_t size, off_t where); + + /** + * Write a block of data + * \param buffer buffer where take data to write + * \param size buffer size + * \param where where to write to + * \return number of bytes written or a negative number on failure + */ + ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + /** + * Write a string. + * An extension to the Device interface that adds a new member function, + * which is used by the kernel on console devices to write debug information + * before the kernel is started or in case of serious errors, right before + * rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * \param str the string to write. The string must be NUL terminated. + */ + void IRQwrite(const char *str); + + /** + * Performs device-specific operations + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int ioctl(int cmd, void *arg); + + /** + * \internal the serial port interrupts call this member function. + * Never call this from user code. + */ + void IRQhandleInterrupt(); + + /** + * Destructor + */ + ~LPC2000Serial(); + +private: + /** + * Wait until all characters have been written to the serial port + */ + void waitSerialTxFifoEmpty() + { + while((serial->LSR & (1<<6))==0) ; + + //This delay has been added to fix a quirk on the Miosix board. When + //writing a message to the console and rebooting, if the reboot happens + //too fast with respect to the last character sent out of the serial + //port, the FT232 gets confused and the last charcters are lost, + //probably from the FT232 buffer. Using delayMs() to be callable from IRQ + delayMs(2); + } + + /** + * The registers of the USART in struct form, to make a generic driver + */ + struct Usart16550 + { + //Offset 0x00 + union { + volatile unsigned char RBR; + volatile unsigned char THR; + volatile unsigned char DLL; + }; + char padding0[3]; + //Offset 0x04 + union { + volatile unsigned char IER; + volatile unsigned char DLM; + }; + char padding1[3]; + //Offset 0x08 + union { + volatile unsigned char IIR; + volatile unsigned char FCR; + }; + char padding2[3]; + //Offset 0x0c + volatile unsigned char LCR; + char padding3[3]; + //Offset 0x10 + volatile unsigned char MCR; //Only USART1 has this + char padding4[3]; + //Offset 0x14 + volatile unsigned char LSR; + char padding5[7]; + //Offset 0x1c + volatile unsigned char SCR; + char padding6[19]; + //Offset 0x30 + volatile unsigned char TER; + }; + + //Configure the software queue here + static const int swTxQueue=32;///< Size of tx software queue + + //The hardware queues cannot be modified, since their length is hardware-specific + static const int hwTxQueueLen=16; + static const int hwRxQueueLen=8; + + FastMutex txMutex;///< Mutex used to guard the tx queue + FastMutex rxMutex;///< Mutex used to guard the rx queue + + Queue txQueue;///< Tx software queue + DynUnsyncQueue rxQueue;///< Rx software queue + Thread *rxWaiting; ///< Thread waiting on rx queue + bool idle; ///< Receiver idle + + Usart16550 *serial; ///< Serial port registers +}; + +} //namespace miosix + +#endif //SERIAL_LPC2000_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_stm32.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/serial_stm32.cpp new file mode 100644 index 00000000..28b791c3 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_stm32.cpp @@ -0,0 +1,1246 @@ +/*************************************************************************** + * Copyright (C) 2010-2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include "serial_stm32.h" +#include "kernel/sync.h" +#include "kernel/scheduler/scheduler.h" +#include "interfaces/portability.h" +#include "filesystem/ioctl.h" +#include "core/cache_cortexMx.h" + +using namespace std; +using namespace miosix; + +static const int numPorts=3; //Supporting only USART1, USART2, USART3 + +//A nice feature of the stm32 is that the USART are connected to the same +//GPIOS in all families, stm32f1, f2, f4 and l1. Additionally, USART1 is +//always connected to the APB2, while USART2 and USART3 are always on APB1 +//Unfortunately, this does not hold with DMA. +typedef Gpio u1tx; +typedef Gpio u1rx; +typedef Gpio u1cts; +typedef Gpio u1rts; + +typedef Gpio u2tx; +typedef Gpio u2rx; +typedef Gpio u2cts; +typedef Gpio u2rts; + +typedef Gpio u3tx; +typedef Gpio u3rx; +typedef Gpio u3cts; +typedef Gpio u3rts; + +/// Pointer to serial port classes to let interrupts access the classes +static STM32Serial *ports[numPorts]={0}; + +/** + * \internal interrupt routine for usart1 actual implementation + */ +void __attribute__((noinline)) usart1irqImpl() +{ + if(ports[0]) ports[0]->IRQhandleInterrupt(); +} + +/** + * \internal interrupt routine for usart1 + */ +void __attribute__((naked)) USART1_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z13usart1irqImplv"); + restoreContext(); +} + +#if !defined(STM32_NO_SERIAL_2_3) + +/** + * \internal interrupt routine for usart2 actual implementation + */ +void __attribute__((noinline)) usart2irqImpl() +{ + if(ports[1]) ports[1]->IRQhandleInterrupt(); +} + +/** + * \internal interrupt routine for usart2 + */ +void __attribute__((naked)) USART2_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z13usart2irqImplv"); + restoreContext(); +} + +#if !defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC) +/** + * \internal interrupt routine for usart3 actual implementation + */ +void __attribute__((noinline)) usart3irqImpl() +{ + if(ports[2]) ports[2]->IRQhandleInterrupt(); +} + +/** + * \internal interrupt routine for usart3 + */ +#if !defined(STM32F072xB) +void __attribute__((naked)) USART3_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z13usart3irqImplv"); + restoreContext(); +} +#else //!defined(STM32F072xB) +void __attribute__((naked)) USART3_4_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z13usart3irqImplv"); + restoreContext(); +} +#endif //!defined(STM32F072xB) +#endif //!defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC) +#endif //!defined(STM32_NO_SERIAL_2_3) + +#ifdef SERIAL_1_DMA + +/** + * \internal USART1 DMA tx actual implementation + */ +void __attribute__((noinline)) usart1txDmaImpl() +{ + #if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + DMA1->IFCR=DMA_IFCR_CGIF4; + DMA1_Channel4->CCR=0; //Disable DMA + #else //stm32f2 and f4 + DMA2->HIFCR=DMA_HIFCR_CTCIF7 + | DMA_HIFCR_CTEIF7 + | DMA_HIFCR_CDMEIF7 + | DMA_HIFCR_CFEIF7; + #endif + if(ports[0]) ports[0]->IRQhandleDMAtx(); +} + +/** + * \internal USART1 DMA rx actual implementation + */ +void __attribute__((noinline)) usart1rxDmaImpl() +{ + if(ports[0]) ports[0]->IRQhandleDMArx(); +} + +#if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) +/** + * \internal DMA1 Channel 4 IRQ (configured as USART1 TX) + */ +void __attribute__((naked)) DMA1_Channel4_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart1txDmaImplv"); + restoreContext(); +} + +/** + * \internal DMA1 Channel 5 IRQ (configured as USART1 RX) + */ +void __attribute__((naked)) DMA1_Channel5_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart1rxDmaImplv"); + restoreContext(); +} + +#else //stm32f2 and stm32f4 + +/** + * \internal DMA2 stream 7 IRQ (configured as USART1 TX) + */ +void __attribute__((naked)) DMA2_Stream7_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart1txDmaImplv"); + restoreContext(); +} + +/** + * \internal DMA2 stream 5 IRQ (configured as USART1 RX) + */ +void __attribute__((naked)) DMA2_Stream5_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart1rxDmaImplv"); + restoreContext(); +} +#endif +#endif //SERIAL_1_DMA + +#if defined(SERIAL_2_DMA) && !defined(STM32_NO_SERIAL_2_3) + +/** + * \internal USART2 DMA tx actual implementation + */ +void __attribute__((noinline)) usart2txDmaImpl() +{ + #if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + DMA1->IFCR=DMA_IFCR_CGIF7; + DMA1_Channel7->CCR=0; //Disable DMA + #else //stm32f2 and f4 + DMA1->HIFCR=DMA_HIFCR_CTCIF6 + | DMA_HIFCR_CTEIF6 + | DMA_HIFCR_CDMEIF6 + | DMA_HIFCR_CFEIF6; + #endif + if(ports[1]) ports[1]->IRQhandleDMAtx(); +} + +/** + * \internal USART2 DMA rx actual implementation + */ +void __attribute__((noinline)) usart2rxDmaImpl() +{ + if(ports[1]) ports[1]->IRQhandleDMArx(); +} + +#if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) +/** + * \internal DMA1 Channel 7 IRQ (configured as USART2 TX) + */ +void __attribute__((naked)) DMA1_Channel7_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart2txDmaImplv"); + restoreContext(); +} + +/** + * \internal DMA1 Channel 6 IRQ (configured as USART2 RX) + */ +void __attribute__((naked)) DMA1_Channel6_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart2rxDmaImplv"); + restoreContext(); +} + +#else //stm32f2 and stm32f4 + +/** + * \internal DMA1 stream 6 IRQ (configured as USART2 TX) + */ +void __attribute__((naked)) DMA1_Stream6_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart2txDmaImplv"); + restoreContext(); +} + +/** + * \internal DMA1 stream 5 IRQ (configured as USART2 RX) + */ +void __attribute__((naked)) DMA1_Stream5_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart2rxDmaImplv"); + restoreContext(); +} +#endif +#endif //SERIAL_2_DMA + +#if defined(SERIAL_3_DMA) && !defined(STM32_NO_SERIAL_2_3) + +/** + * \internal USART3 DMA tx actual implementation + */ +void __attribute__((noinline)) usart3txDmaImpl() +{ + #if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + DMA1->IFCR=DMA_IFCR_CGIF2; + DMA1_Channel2->CCR=0; //Disable DMA + #else //stm32f2 and f4 + DMA1->LIFCR=DMA_LIFCR_CTCIF3 + | DMA_LIFCR_CTEIF3 + | DMA_LIFCR_CDMEIF3 + | DMA_LIFCR_CFEIF3; + #endif + if(ports[2]) ports[2]->IRQhandleDMAtx(); +} + +/** + * \internal USART3 DMA rx actual implementation + */ +void __attribute__((noinline)) usart3rxDmaImpl() +{ + if(ports[2]) ports[2]->IRQhandleDMArx(); +} + +#if defined(_ARCH_CORTEXM3_STM32) || defined (_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) +/** + * \internal DMA1 Channel 2 IRQ (configured as USART3 TX) + */ +void __attribute__((naked)) DMA1_Channel2_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart3txDmaImplv"); + restoreContext(); +} + +/** + * \internal DMA1 Channel 3 IRQ (configured as USART3 RX) + */ +void __attribute__((naked)) DMA1_Channel3_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart3rxDmaImplv"); + restoreContext(); +} + +#else //stm32f2 and stm32f4 + +/** + * \internal DMA1 stream 3 IRQ (configured as USART3 TX) + */ +void __attribute__((naked)) DMA1_Stream3_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart3txDmaImplv"); + restoreContext(); +} + +/** + * \internal DMA1 stream 1 IRQ (configured as USART3 RX) + */ +void __attribute__((naked)) DMA1_Stream1_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15usart3rxDmaImplv"); + restoreContext(); +} +#endif +#endif //SERIAL_3_DMA + +namespace miosix { + +#ifdef SERIAL_DMA +#if defined(_ARCH_CORTEXM4_STM32F4) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) +/** + * The STM3F3, STM32F4 and STM32L4 have an ugly quirk of having 64KB RAM area + * called CCM that can only be accessed by the processor and not be the DMA. + * \param x pointer to check + * \return true if the pointer is inside the CCM, and thus it isn't possible + * to use it for DMA transfers + */ +static bool isInCCMarea(const void *x) +{ + unsigned int ptr=reinterpret_cast(x); + return (ptr>=0x10000000) && (ptr<(0x10000000+64*1024)); +} +#else //_ARCH_CORTEXM4_STM32F4 and _ARCH_CORTEXM4_STM32F3 +static inline bool isInCCMarea(const void *x) { return false; } +#endif // _ARCH_CORTEXM4_STM32F4 and _ARCH_CORTEXM4_STM32F3 +#endif //SERIAL_DMA + +// +// class STM32Serial +// + +// A note on the baudrate/500: the buffer is selected so as to withstand +// 20ms of full data rate. In the 8N1 format one char is made of 10 bits. +// So (baudrate/10)*0.02=baudrate/500 +STM32Serial::STM32Serial(int id, int baudrate, FlowCtrl flowControl) + : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500), + flowControl(flowControl==RTSCTS), portId(id) +{ + #if !defined(_ARCH_CORTEXM3_STM32) + //stm32f2, f4, l4, l1, f7, h7 require alternate function mapping + //stm32f0 family has different alternate function mapping + //with respect to the other families + switch(id) + { + case 1: + #if !defined(_ARCH_CORTEXM0_STM32) + u1tx::alternateFunction(7); + u1rx::alternateFunction(7); + if(flowControl) + { + u1rts::alternateFunction(7); + u1cts::alternateFunction(7); + } + #else //!defined(_ARCH_CORTEXM0_STM32) + u1tx::alternateFunction(1); + u1rx::alternateFunction(1); + if(flowControl) + { + u1rts::alternateFunction(1); + u1cts::alternateFunction(1); + } + #endif //!defined(_ARCH_CORTEXM0_STM32) + break; + case 2: + #if !defined(_ARCH_CORTEXM0_STM32) + u2tx::alternateFunction(7); + u2rx::alternateFunction(7); + if(flowControl) + { + u2rts::alternateFunction(7); + u2cts::alternateFunction(7); + } + #else //!defined(_ARCH_CORTEXM0_STM32) + u2tx::alternateFunction(1); + u2rx::alternateFunction(1); + if(flowControl) + { + u2rts::alternateFunction(1); + u2cts::alternateFunction(1); + } + #endif //!defined(_ARCH_CORTEXM0_STM32) + break; + case 3: + #if !defined(_ARCH_CORTEXM0_STM32) + u3tx::alternateFunction(7); + u3rx::alternateFunction(7); + if(flowControl) + { + u3rts::alternateFunction(7); + u3cts::alternateFunction(7); + } + #else //!defined(_ARCH_CORTEXM0_STM32) + u3tx::alternateFunction(4); + u3rx::alternateFunction(4); + if(flowControl) + { + u3rts::alternateFunction(4); + u3cts::alternateFunction(4); + } + #endif //!defined(_ARCH_CORTEXM0_STM32) + break; + } + #endif //_ARCH_CORTEXM3_STM32 + + switch(id) + { + case 1: + commonInit(id,baudrate,u1tx::getPin(),u1rx::getPin(), + u1rts::getPin(),u1cts::getPin()); + break; + case 2: + commonInit(id,baudrate,u2tx::getPin(),u2rx::getPin(), + u2rts::getPin(),u2cts::getPin()); + break; + case 3: + commonInit(id,baudrate,u3tx::getPin(),u3rx::getPin(), + u3rts::getPin(),u3cts::getPin()); + break; + } +} + +STM32Serial::STM32Serial(int id, int baudrate, GpioPin tx, GpioPin rx) + : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500), + flowControl(false), portId(id) +{ + commonInit(id,baudrate,tx,rx,tx,rx); //The last two args will be ignored +} + +STM32Serial::STM32Serial(int id, int baudrate, GpioPin tx, GpioPin rx, + miosix::GpioPin rts, miosix::GpioPin cts) + : Device(Device::TTY), rxQueue(rxQueueMin+baudrate/500), + flowControl(true), portId(id) +{ + commonInit(id,baudrate,tx,rx,rts,cts); +} + +void STM32Serial::commonInit(int id, int baudrate, GpioPin tx, GpioPin rx, + GpioPin rts, GpioPin cts) +{ + #ifdef SERIAL_DMA + dmaTx=0; + dmaRx=0; + txWaiting=0; + dmaTxInProgress=false; + #endif //SERIAL_DMA + InterruptDisableLock dLock; + if(id<1|| id>numPorts || ports[id-1]!=0) errorHandler(UNEXPECTED); + ports[id-1]=this; + unsigned int freq=SystemCoreClock; + //Quirk the position of the PPRE1 and PPRE2 bitfields in RCC->CFGR changes + //STM32F0 does not have ppre1 and ppre2, in this case the variables are not + //defined in order to avoid "unused variable" warning + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM3_STM32L1) \ + || defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4) + const unsigned int ppre1=8; + const unsigned int ppre2=11; + #elif !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32) + const unsigned int ppre1=10; + const unsigned int ppre2=13; + #endif + switch(id) + { + case 1: + port=USART1; + RCC->APB2ENR |= RCC_APB2ENR_USART1EN; + RCC_SYNC(); + #ifdef SERIAL_1_DMA + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + #ifdef _ARCH_CORTEXM4_STM32L4 + RCC->AHB1ENR |= RCC_AHBENR_DMA1EN; + DMA1_CSELR->CSELR |= (2 << DMA_CSELR_C4S_Pos) // Assign DMA1_CH4 to USART1_TX + | (2 << DMA_CSELR_C5S_Pos);// Assign DMA1_CH5 to USART1_RX + #else + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + #endif + RCC_SYNC(); + NVIC_SetPriority(DMA1_Channel4_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(DMA1_Channel4_IRQn); + dmaTx=DMA1_Channel4; + //Higher priority to ensure IRQhandleDMArx() is called before + //IRQhandleInterrupt(), so that idle is set correctly + NVIC_SetPriority(DMA1_Channel5_IRQn,14); + NVIC_EnableIRQ(DMA1_Channel5_IRQn); + dmaRx=DMA1_Channel5; + #else //stm32f2, stm32f4 + RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; + RCC_SYNC(); + NVIC_SetPriority(DMA2_Stream7_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(DMA2_Stream7_IRQn); + dmaTx=DMA2_Stream7; + //Higher priority to ensure IRQhandleDMArx() is called before + //IRQhandleInterrupt(), so that idle is set correctly + NVIC_SetPriority(DMA2_Stream5_IRQn,14); + NVIC_EnableIRQ(DMA2_Stream5_IRQn); + dmaRx=DMA2_Stream5; + #endif + port->CR3=USART_CR3_DMAT | USART_CR3_DMAR; + #endif //SERIAL_1_DMA + NVIC_SetPriority(USART1_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(USART1_IRQn); + #if !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32) + if(RCC->CFGR & RCC_CFGR_PPRE2_2) freq/=1<<(((RCC->CFGR>>ppre2) & 0x3)+1); + #elif defined(_ARCH_CORTEXM0_STM32) + // STM32F0 family has only PPRE2 register + if(RCC->CFGR & RCC_CFGR_PPRE_2) freq/=1<<(((RCC->CFGR>>8) & 0x3)+1); + #else + //rcc_hclk3 = SystemCoreClock / HPRE + //rcc_pclk2 = rcc_hclk1 / D2PPRE2 + //NOTE: are rcc_hclk3 and rcc_hclk1 the same signal? + //usart1 clock is rcc_pclk2 + if(RCC->D1CFGR & RCC_D1CFGR_HPRE_3) + freq/=1<<(((RCC->D1CFGR>>RCC_D1CFGR_HPRE_Pos) & 0x7)+1); + if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE2_2) + freq/=1<<(((RCC->D2CFGR>>RCC_D2CFGR_D2PPRE2_Pos) & 0x3)+1); + #endif //_ARCH_CORTEXM7_STM32H7 + break; + + #if !defined(STM32_NO_SERIAL_2_3) + case 2: + port=USART2; + #ifndef _ARCH_CORTEXM7_STM32H7 + #ifndef _ARCH_CORTEXM4_STM32L4 + RCC->APB1ENR |= RCC_APB1ENR_USART2EN; + #else //_ARCH_CORTEXM4_STM32L4 + RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN; + #endif //_ARCH_CORTEXM4_STM32L4 + #else //_ARCH_CORTEXM7_STM32H7 + RCC->APB1LENR |= RCC_APB1LENR_USART2EN; + #endif //_ARCH_CORTEXM7_STM32H7 + RCC_SYNC(); + #ifdef SERIAL_2_DMA + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + #ifdef _ARCH_CORTEXM4_STM32L4 + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + DMA1_CSELR->CSELR |= (2 << DMA_CSELR_C7S_Pos) // Assign DMA1_CH7 to USART2_TX + | (2 << DMA_CSELR_C6S_Pos);// Assign DMA1_CH6 to USART2_RX + #else + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + #endif + RCC_SYNC(); + NVIC_SetPriority(DMA1_Channel7_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(DMA1_Channel7_IRQn); + dmaTx=DMA1_Channel7; + //Higher priority to ensure IRQhandleDMArx() is called before + //IRQhandleInterrupt(), so that idle is set correctly + NVIC_SetPriority(DMA1_Channel6_IRQn,14); + NVIC_EnableIRQ(DMA1_Channel6_IRQn); + dmaRx=DMA1_Channel6; + #else //stm32f2, stm32f4 + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + RCC_SYNC(); + NVIC_SetPriority(DMA1_Stream6_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(DMA1_Stream6_IRQn); + dmaTx=DMA1_Stream6; + //Higher priority to ensure IRQhandleDMArx() is called before + //IRQhandleInterrupt(), so that idle is set correctly + NVIC_SetPriority(DMA1_Stream5_IRQn,14); + NVIC_EnableIRQ(DMA1_Stream5_IRQn); + dmaRx=DMA1_Stream5; + #endif + port->CR3=USART_CR3_DMAT | USART_CR3_DMAR; + #endif //SERIAL_2_DMA + NVIC_SetPriority(USART2_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(USART2_IRQn); + #if !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32) + if(RCC->CFGR & RCC_CFGR_PPRE1_2) freq/=1<<(((RCC->CFGR>>ppre1) & 0x3)+1); + #elif defined(_ARCH_CORTEXM0_STM32) + // STM32F0 family has only PPRE2 register + if(RCC->CFGR & RCC_CFGR_PPRE_2) freq/=1<<(((RCC->CFGR>>8) & 0x3)+1); + #else //_ARCH_CORTEXM7_STM32H7 + //rcc_hclk3 = SystemCoreClock / HPRE + //rcc_pclk1 = rcc_hclk1 / D2PPRE1 + //NOTE: are rcc_hclk3 and rcc_hclk1 the same signal? + //usart2 clock is rcc_pclk1 + if(RCC->D1CFGR & RCC_D1CFGR_HPRE_3) + freq/=1<<(((RCC->D1CFGR>>RCC_D1CFGR_HPRE_Pos) & 0x7)+1); + if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE1_2) + freq/=1<<(((RCC->D2CFGR>>RCC_D2CFGR_D2PPRE1_Pos) & 0x3)+1); + #endif //_ARCH_CORTEXM7_STM32H7 + break; + #if !defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC) + case 3: + port=USART3; + #ifndef _ARCH_CORTEXM7_STM32H7 + #ifndef _ARCH_CORTEXM4_STM32L4 + RCC->APB1ENR |= RCC_APB1ENR_USART3EN; + #else //_ARCH_CORTEXM4_STM32L4 + RCC->APB1ENR1 |= RCC_APB1ENR1_USART3EN; + #endif //_ARCH_CORTEXM4_STM32L4 + #else + RCC->APB1LENR |= RCC_APB1LENR_USART3EN; + #endif //_ARCH_CORTEXM7_STM32H7 + RCC_SYNC(); + #ifdef SERIAL_3_DMA + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + #ifdef _ARCH_CORTEXM4_STM32L4 + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + DMA1_CSELR->CSELR |= (2 << DMA_CSELR_C2S_Pos) // Assign DMA1_CH2 to USART2_TX + | (2 << DMA_CSELR_C3S_Pos);// Assign DMA1_CH3 to USART2_RX + #else + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + #endif + RCC_SYNC(); + NVIC_SetPriority(DMA1_Channel2_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(DMA1_Channel2_IRQn); + dmaTx=DMA1_Channel2; + //Higher priority to ensure IRQhandleDMArx() is called before + //IRQhandleInterrupt(), so that idle is set correctly + NVIC_SetPriority(DMA1_Channel3_IRQn,14); + NVIC_EnableIRQ(DMA1_Channel3_IRQn); + dmaRx=DMA1_Channel3; + #else //stm32f2, stm32f4 + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + RCC_SYNC(); + NVIC_SetPriority(DMA1_Stream3_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(DMA1_Stream3_IRQn); + dmaTx=DMA1_Stream3; + //Higher priority to ensure IRQhandleDMArx() is called before + //IRQhandleInterrupt(), so that idle is set correctly + NVIC_SetPriority(DMA1_Stream1_IRQn,14); + NVIC_EnableIRQ(DMA1_Stream1_IRQn); + dmaRx=DMA1_Stream1; + #endif + port->CR3=USART_CR3_DMAT | USART_CR3_DMAR; + #endif //SERIAL_3_DMA + #if !defined(STM32F072xB) + NVIC_SetPriority(USART3_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(USART3_IRQn); + #else //STM32F072xB + NVIC_SetPriority(USART3_4_IRQn,15); + NVIC_EnableIRQ(USART3_4_IRQn); + #endif //STM32F072xB + #if !defined(_ARCH_CORTEXM7_STM32H7) && !defined(_ARCH_CORTEXM0_STM32) + if(RCC->CFGR & RCC_CFGR_PPRE1_2) freq/=1<<(((RCC->CFGR>>ppre1) & 0x3)+1); + #elif defined(_ARCH_CORTEXM0_STM32) + // STM32F0 family has only PPRE2 register + if(RCC->CFGR & RCC_CFGR_PPRE_2) freq/=1<<(((RCC->CFGR>>8) & 0x3)+1); + #else //_ARCH_CORTEXM7_STM32H7 + //rcc_hclk3 = SystemCoreClock / HPRE + //rcc_pclk1 = rcc_hclk1 / D2PPRE1 + //NOTE: are rcc_hclk3 and rcc_hclk1 the same signal? + //usart2 clock is rcc_pclk1 + if(RCC->D1CFGR & RCC_D1CFGR_HPRE_3) + freq/=1<<(((RCC->D1CFGR>>RCC_D1CFGR_HPRE_Pos) & 0x7)+1); + if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE1_2) + freq/=1<<(((RCC->D2CFGR>>RCC_D2CFGR_D2PPRE1_Pos) & 0x3)+1); + #endif //_ARCH_CORTEXM7_STM32H7 + break; + #endif //!defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC) + #endif //!defined(STM32_NO_SERIAL_2_3) + } + //Quirk: stm32f1 rx pin has to be in input mode, while stm32f2 and up want + //it in ALTERNATE mode. Go figure... + #ifdef _ARCH_CORTEXM3_STM32 + Mode::Mode_ rxPinMode=Mode::INPUT; + #else //_ARCH_CORTEXM3_STM32 + Mode::Mode_ rxPinMode=Mode::ALTERNATE; + #endif //_ARCH_CORTEXM3_STM32 + tx.mode(Mode::ALTERNATE); + rx.mode(rxPinMode); + if(flowControl) + { + rts.mode(Mode::ALTERNATE); + cts.mode(rxPinMode); + } + const unsigned int quot=2*freq/baudrate; //2*freq for round to nearest + port->BRR=quot/2 + (quot & 1); //Round to nearest + if(flowControl==false) port->CR3 |= USART_CR3_ONEBIT; + else port->CR3 |= USART_CR3_ONEBIT | USART_CR3_RTSE | USART_CR3_CTSE; + //Enabled, 8 data bit, no parity, interrupt on character rx + #ifdef SERIAL_DMA + if(dmaTx) + { + port->CR1 = USART_CR1_UE //Enable port + | USART_CR1_IDLEIE //Interrupt on idle line + | USART_CR1_TE //Transmission enbled + | USART_CR1_RE; //Reception enabled + IRQdmaReadStart(); + return; + } + #endif //SERIAL_DMA + port->CR1 = USART_CR1_UE //Enable port + | USART_CR1_RXNEIE //Interrupt on data received + | USART_CR1_IDLEIE //Interrupt on idle line + | USART_CR1_TE //Transmission enbled + | USART_CR1_RE; //Reception enabled +} + +ssize_t STM32Serial::readBlock(void *buffer, size_t size, off_t where) +{ + Lock l(rxMutex); + char *buf=reinterpret_cast(buffer); + size_t result=0; + FastInterruptDisableLock dLock; + for(;;) + { + //Try to get data from the queue + for(;result0) break; + if(result==size) break; + //Wait for data in the queue + do { + rxWaiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(rxWaiting); + } + return result; +} + +ssize_t STM32Serial::writeBlock(const void *buffer, size_t size, off_t where) +{ + Lock l(txMutex); + const char *buf=reinterpret_cast(buffer); + #ifdef SERIAL_DMA + if(dmaTx) + { + size_t remaining=size; + if(isInCCMarea(buf)==false) + { + //Use zero copy for all but the last txBufferSize bytes, if possible + while(remaining>txBufferSize) + { + //DMA is limited to 64K + size_t transferSize=min(remaining-txBufferSize,65535); + waitDmaTxCompletion(); + writeDma(buf,transferSize); + buf+=transferSize; + remaining-=transferSize; + } + } + while(remaining>0) + { + size_t transferSize=min(remaining,static_cast(txBufferSize)); + waitDmaTxCompletion(); + //Copy to txBuffer only after DMA xfer completed, as the previous + //xfer may be using the same buffer + memcpy(txBuffer,buf,transferSize); + writeDma(txBuffer,transferSize); + buf+=transferSize; + remaining-=transferSize; + } + return size; + } + #endif //SERIAL_DMA + for(size_t i=0;iSR & USART_SR_TXE)==0) ; + port->DR=*buf++; + #else //_ARCH_CORTEXM7_STM32F7/H7 + while((port->ISR & USART_ISR_TXE)==0) ; + port->TDR=*buf++; + #endif //_ARCH_CORTEXM7_STM32F7/H7 + } + return size; +} + +void STM32Serial::IRQwrite(const char *str) +{ + // We can reach here also with only kernel paused, so make sure + // interrupts are disabled. This is important for the DMA case + bool interrupts=areInterruptsEnabled(); + if(interrupts) fastDisableInterrupts(); + #ifdef SERIAL_DMA + if(dmaTx) + { + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + //If no DMA transfer is in progress bit EN is zero. Otherwise wait until + //DMA xfer ends, by waiting for the TC (or TE) interrupt flag + static const unsigned int irqMask[]= + { + (DMA_ISR_TCIF4 | DMA_ISR_TEIF4), + (DMA_ISR_TCIF7 | DMA_ISR_TEIF7), + (DMA_ISR_TCIF2 | DMA_ISR_TEIF2) + }; + #if defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4) + // Workaround for ST messing up with flag definitions... + constexpr unsigned int DMA_CCR4_EN = DMA_CCR_EN; + #endif + while((dmaTx->CCR & DMA_CCR4_EN) && !(DMA1->ISR & irqMask[getId()-1])) ; + #else //_ARCH_CORTEXM3_STM32 + //Wait until DMA xfer ends. EN bit is cleared by hardware on transfer end + while(dmaTx->CR & DMA_SxCR_EN) ; + #endif //_ARCH_CORTEXM3_STM32 + } + #endif //SERIAL_DMA + while(*str) + { + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) && !defined(_ARCH_CORTEXM4_STM32F3) \ + && !defined(_ARCH_CORTEXM4_STM32L4) + while((port->SR & USART_SR_TXE)==0) ; + port->DR=*str++; + #else //_ARCH_CORTEXM7_STM32F7/H7 + while((port->ISR & USART_ISR_TXE)==0) ; + port->TDR=*str++; + #endif //_ARCH_CORTEXM7_STM32F7/H7 + } + waitSerialTxFifoEmpty(); + if(interrupts) fastEnableInterrupts(); +} + +int STM32Serial::ioctl(int cmd, void* arg) +{ + if(reinterpret_cast(arg) & 0b11) return -EFAULT; //Unaligned + termios *t=reinterpret_cast(arg); + switch(cmd) + { + case IOCTL_SYNC: + waitSerialTxFifoEmpty(); + return 0; + case IOCTL_TCGETATTR: + t->c_iflag=IGNBRK | IGNPAR; + t->c_oflag=0; + t->c_cflag=CS8 | (flowControl ? CRTSCTS : 0); + t->c_lflag=0; + return 0; + case IOCTL_TCSETATTR_NOW: + case IOCTL_TCSETATTR_DRAIN: + case IOCTL_TCSETATTR_FLUSH: + //Changing things at runtime unsupported, so do nothing, but don't + //return error as console_device.h implements some attribute changes + return 0; + default: + return -ENOTTY; //Means the operation does not apply to this descriptor + } +} + +void STM32Serial::IRQhandleInterrupt() +{ + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) && !defined(_ARCH_CORTEXM4_STM32F3) \ + && !defined(_ARCH_CORTEXM4_STM32L4) + unsigned int status=port->SR; + #else //_ARCH_CORTEXM7_STM32F7/H7 + unsigned int status=port->ISR; + constexpr unsigned int USART_SR_RXNE=USART_ISR_RXNE; + constexpr unsigned int USART_SR_IDLE=USART_ISR_IDLE; + constexpr unsigned int USART_SR_FE =USART_ISR_FE; + #endif //_ARCH_CORTEXM7_STM32F7/H7 + char c; + #ifdef SERIAL_DMA + if(dmaRx==0 && (status & USART_SR_RXNE)) + #else //SERIAL_DMA + if(status & USART_SR_RXNE) + #endif //SERIAL_DMA + { + //Always read data, since this clears interrupt flags + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) && !defined(_ARCH_CORTEXM4_STM32F3) \ + && !defined(_ARCH_CORTEXM4_STM32L4) + c=port->DR; + #else //_ARCH_CORTEXM7_STM32F7/H7 + c=port->RDR; + #endif //_ARCH_CORTEXM7_STM32F7/H7 + //If no error put data in buffer + if((status & USART_SR_FE)==0) + if(rxQueue.tryPut(c)==false) /*fifo overflow*/; + idle=false; + } + if(status & USART_SR_IDLE) + { + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) && !defined(_ARCH_CORTEXM4_STM32F3) \ + && !defined(_ARCH_CORTEXM4_STM32L4) + c=port->DR; //clears interrupt flags + #else //_ARCH_CORTEXM7_STM32F7/H7 + port->ICR=USART_ICR_IDLECF; //clears interrupt flags + #endif //_ARCH_CORTEXM7_STM32F7/H7 + #ifdef SERIAL_DMA + if(dmaRx) IRQreadDma(); + #endif //SERIAL_DMA + idle=true; + } + if((status & USART_SR_IDLE) || rxQueue.size()>=rxQueueMin) + { + //Enough data in buffer or idle line, awake thread + if(rxWaiting) + { + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + rxWaiting=0; + } + } +} + +#ifdef SERIAL_DMA +void STM32Serial::IRQhandleDMAtx() +{ + dmaTxInProgress=false; + if(txWaiting==0) return; + txWaiting->IRQwakeup(); + if(txWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + txWaiting=0; +} + +void STM32Serial::IRQhandleDMArx() +{ + IRQreadDma(); + idle=false; + if(rxWaiting==0) return; + rxWaiting->IRQwakeup(); + if(rxWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + rxWaiting=0; +} +#endif //SERIAL_DMA + +STM32Serial::~STM32Serial() +{ + waitSerialTxFifoEmpty(); + { + InterruptDisableLock dLock; + port->CR1=0; + int id=getId(); + ports[id-1]=0; + switch(id) + { + case 1: + #ifdef SERIAL_1_DMA + IRQdmaReadStop(); + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + NVIC_DisableIRQ(DMA1_Channel4_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel4_IRQn); + NVIC_DisableIRQ(DMA1_Channel5_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel5_IRQn); + #else //stm32f2, stm32f4 + NVIC_DisableIRQ(DMA2_Stream7_IRQn); + NVIC_ClearPendingIRQ(DMA2_Stream7_IRQn); + NVIC_DisableIRQ(DMA2_Stream5_IRQn); + NVIC_ClearPendingIRQ(DMA2_Stream5_IRQn); + #endif + #endif //SERIAL_1_DMA + NVIC_DisableIRQ(USART1_IRQn); + NVIC_ClearPendingIRQ(USART1_IRQn); + RCC->APB2ENR &= ~RCC_APB2ENR_USART1EN; + break; + + #if !defined(STM32_NO_SERIAL_2_3) + case 2: + #ifdef SERIAL_2_DMA + IRQdmaReadStop(); + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + NVIC_DisableIRQ(DMA1_Channel7_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel7_IRQn); + NVIC_DisableIRQ(DMA1_Channel6_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel6_IRQn); + #else //stm32f2, stm32f4 + NVIC_DisableIRQ(DMA1_Stream6_IRQn); + NVIC_ClearPendingIRQ(DMA1_Stream6_IRQn); + NVIC_DisableIRQ(DMA1_Stream5_IRQn); + NVIC_ClearPendingIRQ(DMA1_Stream5_IRQn); + #endif + #endif //SERIAL_2_DMA + NVIC_DisableIRQ(USART2_IRQn); + NVIC_ClearPendingIRQ(USART2_IRQn); + #ifndef _ARCH_CORTEXM7_STM32H7 + #ifdef _ARCH_CORTEXM4_STM32L4 + RCC->APB1ENR1 &= ~RCC_APB1ENR1_USART2EN; + #else + RCC->APB1ENR &= ~RCC_APB1ENR_USART2EN; + #endif + #else //_ARCH_CORTEXM7_STM32H7 + RCC->APB1LENR &= ~RCC_APB1LENR_USART2EN; + #endif //_ARCH_CORTEXM7_STM32H7 + break; + #if !defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC) + case 3: + #ifdef SERIAL_3_DMA + IRQdmaReadStop(); + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + NVIC_DisableIRQ(DMA1_Channel2_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel2_IRQn); + NVIC_DisableIRQ(DMA1_Channel3_IRQn); + NVIC_ClearPendingIRQ(DMA1_Channel3_IRQn); + #else //stm32f2, stm32f4 + NVIC_DisableIRQ(DMA1_Stream3_IRQn); + NVIC_ClearPendingIRQ(DMA1_Stream3_IRQn); + NVIC_DisableIRQ(DMA1_Stream1_IRQn); + NVIC_ClearPendingIRQ(DMA1_Stream1_IRQn); + #endif + #endif //SERIAL_3_DMA + #if !defined(STM32F072xB) + NVIC_SetPriority(USART3_IRQn,15);//Lowest priority for serial + NVIC_EnableIRQ(USART3_IRQn); + #else //STM32F072xB + NVIC_SetPriority(USART3_4_IRQn,15); + NVIC_EnableIRQ(USART3_4_IRQn); + #endif //STM32F072xB + #ifndef _ARCH_CORTEXM7_STM32H7 + #ifdef _ARCH_CORTEXM4_STM32L4 + RCC->APB1ENR1 &= ~RCC_APB1ENR1_USART3EN; + #else + RCC->APB1ENR &= ~RCC_APB1ENR_USART3EN; + #endif + #else //_ARCH_CORTEXM7_STM32H7 + RCC->APB1LENR &= ~RCC_APB1LENR_USART3EN; + #endif //_ARCH_CORTEXM7_STM32H7 + break; + #endif //!defined(STM32F411xE) && !defined(STM32F401xE) && !defined(STM32F401xC) + #endif //!defined(STM32_NO_SERIAL_2_3) + } + } +} + +#ifdef SERIAL_DMA +void STM32Serial::waitDmaTxCompletion() +{ + FastInterruptDisableLock dLock; + // If a previous DMA xfer is in progress, wait + if(dmaTxInProgress) + { + txWaiting=Thread::IRQgetCurrentThread(); + do { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(txWaiting); + } +} + +void STM32Serial::writeDma(const char *buffer, size_t size) +{ + markBufferBeforeDmaWrite(buffer,size); + //Quirk: DMA messes up the TC bit, and causes waitSerialTxFifoEmpty() to + //return prematurely, causing characters to be missed when rebooting + //immediatley a write. You can just clear the bit manually, but doing that + //is dangerous, as if you clear the bit but for any reason the serial + //write doesn't start (think an invalid buffer, or another thread crashing), + //then TC will never be set and waitSerialTxFifoEmpty() deadlocks! + //The only way to clear it safely is to first read SR and then write to + //DR (thus the bit is cleared at the same time a transmission is started, + //and the race condition is eliminated). This is the purpose of this + //instruction, it reads SR. When we start the DMA, the DMA controller + //writes to DR and completes the TC clear sequence. + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) && !defined(_ARCH_CORTEXM4_STM32F3) \ + && !defined(_ARCH_CORTEXM4_STM32L4) + while((port->SR & USART_SR_TXE)==0) ; + #else //_ARCH_CORTEXM7_STM32F7/H7 + while((port->ISR & USART_ISR_TXE)==0) ; + #endif //_ARCH_CORTEXM7_STM32F7/H7 + + dmaTxInProgress=true; + #if defined(_ARCH_CORTEXM3_STM32) + dmaTx->CPAR=reinterpret_cast(&port->DR); + dmaTx->CMAR=reinterpret_cast(buffer); + dmaTx->CNDTR=size; + dmaTx->CCR=DMA_CCR4_MINC //Increment RAM pointer + | DMA_CCR4_DIR //Memory to peripheral + | DMA_CCR4_TEIE //Interrupt on transfer error + | DMA_CCR4_TCIE //Interrupt on transfer complete + | DMA_CCR4_EN; //Start DMA + #else + #if defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4) + dmaTx->CPAR=reinterpret_cast(&port->TDR); + dmaTx->CMAR=reinterpret_cast(buffer); + dmaTx->CNDTR=size; + dmaTx->CCR=DMA_CCR_MINC //Increment RAM pointer + | DMA_CCR_DIR //Memory to peripheral + | DMA_CCR_TEIE //Interrupt on transfer error + | DMA_CCR_TCIE //Interrupt on transfer complete + | DMA_CCR_EN; //Start DMA + #else //_ARCH_CORTEXM4_STM32F3 + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) + dmaTx->PAR=reinterpret_cast(&port->DR); + #else //_ARCH_CORTEXM7_STM32F7/H7 + dmaTx->PAR=reinterpret_cast(&port->TDR); + #endif //_ARCH_CORTEXM7_STM32F7/H7 + dmaTx->M0AR=reinterpret_cast(buffer); + dmaTx->NDTR=size; + //Quirk: not enabling DMA_SxFCR_FEIE because the USART seems to + //generate a spurious fifo error. The code was tested and the + //transfer completes successfully even in the presence of this fifo + //error + dmaTx->FCR=DMA_SxFCR_DMDIS;//Enable fifo + dmaTx->CR=DMA_SxCR_CHSEL_2 //Select channel 4 (USART_TX) + | DMA_SxCR_MINC //Increment RAM pointer + | DMA_SxCR_DIR_0 //Memory to peripheral + | DMA_SxCR_TCIE //Interrupt on completion + | DMA_SxCR_TEIE //Interrupt on transfer error + | DMA_SxCR_DMEIE //Interrupt on direct mode error + | DMA_SxCR_EN; //Start the DMA + #endif //_ARCH_CORTEXM4_STM32F3 + #endif //_ARCH_CORTEXM3_STM32 +} + +void STM32Serial::IRQreadDma() +{ + int elem=IRQdmaReadStop(); + markBufferAfterDmaRead(rxBuffer,rxQueueMin); + for(int i=0;iCPAR=reinterpret_cast(&port->DR); + dmaRx->CMAR=reinterpret_cast(rxBuffer); + dmaRx->CNDTR=rxQueueMin; + dmaRx->CCR=DMA_CCR4_MINC //Increment RAM pointer + | 0 //Peripheral to memory + | DMA_CCR4_TEIE //Interrupt on transfer error + | DMA_CCR4_TCIE //Interrupt on transfer complete + | DMA_CCR4_EN; //Start DMA + #else + #if defined(_ARCH_CORTEXM4_STM32F3) || defined(_ARCH_CORTEXM4_STM32L4) + dmaRx->CPAR=reinterpret_cast(&port->RDR); + dmaRx->CMAR=reinterpret_cast(rxBuffer); + dmaRx->CNDTR=rxQueueMin; + dmaRx->CCR=DMA_CCR_MINC //Increment RAM pointer + | 0 //Peripheral to memory + | DMA_CCR_TEIE //Interrupt on transfer error + | DMA_CCR_TCIE //Interrupt on transfer complete + | DMA_CCR_EN; //Start DMA + #else //_ARCH_CORTEXM4_STM32F3 + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) + dmaRx->PAR=reinterpret_cast(&port->DR); + #else //_ARCH_CORTEXM7_STM32F7/H7 + dmaRx->PAR=reinterpret_cast(&port->RDR); + #endif //_ARCH_CORTEXM7_STM32F7/H7 + dmaRx->M0AR=reinterpret_cast(rxBuffer); + dmaRx->NDTR=rxQueueMin; + dmaRx->CR=DMA_SxCR_CHSEL_2 //Select channel 4 (USART_RX) + | DMA_SxCR_MINC //Increment RAM pointer + | 0 //Peripheral to memory + | DMA_SxCR_HTIE //Interrupt on half transfer + | DMA_SxCR_TEIE //Interrupt on transfer error + | DMA_SxCR_DMEIE //Interrupt on direct mode error + | DMA_SxCR_EN; //Start the DMA + #endif //_ARCH_CORTEXM4_STM32F3 + #endif //_ARCH_CORTEXM3_STM32 +} + +int STM32Serial::IRQdmaReadStop() +{ + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + dmaRx->CCR=0; + static const unsigned int irqMask[]= + { + DMA_IFCR_CGIF5, + DMA_IFCR_CGIF6, + DMA_IFCR_CGIF3 + }; + DMA1->IFCR=irqMask[getId()-1]; + return rxQueueMin-dmaRx->CNDTR; + #else //_ARCH_CORTEXM3_STM32 + //Stop DMA and wait for it to actually stop + dmaRx->CR &= ~DMA_SxCR_EN; + while(dmaRx->CR & DMA_SxCR_EN) ; + static const unsigned int irqMask[]= + { + (DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5), + (DMA_HIFCR_CTCIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5), + (DMA_LIFCR_CTCIF1 | DMA_LIFCR_CHTIF1 | DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CFEIF1) + }; + static volatile unsigned long * const irqRegs[]= + { + &DMA2->HIFCR, + &DMA1->HIFCR, + &DMA1->LIFCR + }; + *irqRegs[getId()-1]=irqMask[getId()-1]; + return rxQueueMin-dmaRx->NDTR; + #endif //_ARCH_CORTEXM3_STM32 +} +#endif //SERIAL_DMA + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/serial_stm32.h b/lib/miosix-kernel/miosix/arch/common/drivers/serial_stm32.h new file mode 100644 index 00000000..95ad8d04 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/serial_stm32.h @@ -0,0 +1,295 @@ +/*************************************************************************** + * Copyright (C) 2010-2018 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SERIAL_STM32_H +#define SERIAL_STM32_H + +#include "filesystem/console/console_device.h" +#include "kernel/sync.h" +#include "kernel/queue.h" +#include "interfaces/gpio.h" +#include "board_settings.h" + +#if defined(_ARCH_CORTEXM3_STM32) && defined(__ENABLE_XRAM) +//Quirk: concurrent access to the FSMC from both core and DMA is broken in +//the stm32f1, so disable DMA mode if XRAM is enabled. +#undef SERIAL_1_DMA +#undef SERIAL_2_DMA +#undef SERIAL_3_DMA +#endif + +#if defined(SERIAL_1_DMA) || defined(SERIAL_2_DMA) || defined(SERIAL_3_DMA) +#define SERIAL_DMA +#endif + +#if defined(SERIAL_DMA) && defined(_ARCH_CORTEXM0_STM32) +#undef SERIAL_1_DMA +#undef SERIAL_2_DMA +#undef SERIAL_3_DMA +#undef SERIAL_DMA +#warning "DMA not yet implemented for STM32F0 family" +#endif + +namespace miosix { + +/** + * Serial port class for stm32 microcontrollers. + * Only supports USART1, USART2 and USART3 + * Additionally, USARTx can use DMA if SERIAL_x_DMA is defined in + * board_settings.h, while the other serial use polling for transmission, + * and interrupt for reception. + * + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + */ +class STM32Serial : public Device +{ +public: + enum FlowCtrl + { + NOFLOWCTRL, ///< No hardware flow control + RTSCTS ///< RTS/CTS hardware flow control + }; + + /** + * Constructor, initializes the serial port using the default pins, which + * are: + * USART1: tx=PA9 rx=PA10 cts=PA11 rts=PA12 + * USART2: tx=PA2 rx=PA3 cts=PA0 rts=PA1 + * USART3: tx=PB10 rx=PB11 cts=PB13 rts=PB14 + * If you board has a different mapping, use one of the other constructors. + * + * Calls errorHandler(UNEXPECTED) if id is not in the correct range, or when + * attempting to construct multiple objects with the same id. That is, + * it is possible to instantiate only one instance of this class for each + * hardware USART. + * \param id a number 1 to 3 to select which USART + * \param baudrate serial port baudrate + * \param flowControl to enable hardware flow control on this port + */ + STM32Serial(int id, int baudrate, FlowCtrl flowControl=NOFLOWCTRL); + + /** + * Constructor, initializes the serial port using remapped pins and disables + * flow control. + * + * NOTE: for stm32f2, f4, f7 and h7 you have to set the correct alternate + * function to the pins in order to connect then to the USART peripheral + * before passing them to this class. + * + * Calls errorHandler(UNEXPECTED) if id is not in the correct range, or when + * attempting to construct multiple objects with the same id. That is, + * it is possible to instantiate only one instance of this class for each + * hardware USART. + * \param id a number 1 to 3 to select which USART + * \param baudrate serial port baudrate + * \param tx tx pin + * \param rx rx pin + */ + STM32Serial(int id, int baudrate, miosix::GpioPin tx, miosix::GpioPin rx); + + /** + * Constructor, initializes the serial port using remapped pins and enables + * flow control. + * + * NOTE: for stm32f2, f4, f7 and h7 you have to set the correct alternate + * function to the pins in order to connect then to the USART peripheral + * before passing them to this class. + * + * Calls errorHandler(UNEXPECTED) if id is not in the correct range, or when + * attempting to construct multiple objects with the same id. That is, + * it is possible to instantiate only one instance of this class for each + * hardware USART. + * \param id a number 1 to 3 to select which USART + * \param tx tx pin + * \param rx rx pin + * \param rts rts pin + * \param cts cts pin + */ + STM32Serial(int id, int baudrate, miosix::GpioPin tx, miosix::GpioPin rx, + miosix::GpioPin rts, miosix::GpioPin cts); + + /** + * Read a block of data + * \param buffer buffer where read data will be stored + * \param size buffer size + * \param where where to read from + * \return number of bytes read or a negative number on failure. Note that + * it is normal for this function to return less character than the amount + * asked + */ + ssize_t readBlock(void *buffer, size_t size, off_t where); + + /** + * Write a block of data + * \param buffer buffer where take data to write + * \param size buffer size + * \param where where to write to + * \return number of bytes written or a negative number on failure + */ + ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + /** + * Write a string. + * An extension to the Device interface that adds a new member function, + * which is used by the kernel on console devices to write debug information + * before the kernel is started or in case of serious errors, right before + * rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * \param str the string to write. The string must be NUL terminated. + */ + void IRQwrite(const char *str); + + /** + * Performs device-specific operations + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int ioctl(int cmd, void *arg); + + /** + * \internal the serial port interrupts call this member function. + * Never call this from user code. + */ + void IRQhandleInterrupt(); + + #ifdef SERIAL_DMA + /** + * \internal the serial port DMA tx interrupts call this member function. + * Never call this from user code. + */ + void IRQhandleDMAtx(); + + /** + * \internal the serial port DMA rx interrupts call this member function. + * Never call this from user code. + */ + void IRQhandleDMArx(); + #endif //SERIAL_DMA + + /** + * \return port id, 1 for USART1, 2 for USART2, ... + */ + int getId() const { return portId; } + + /** + * Destructor + */ + ~STM32Serial(); + +private: + /** + * Code common for all constructors + */ + void commonInit(int id, int baudrate, miosix::GpioPin tx, miosix::GpioPin rx, + miosix::GpioPin rts, miosix::GpioPin cts); + + #ifdef SERIAL_DMA + /** + * Wait until a pending DMA TX completes, if any + */ + void waitDmaTxCompletion(); + + /** + * Write to the serial port using DMA. When the function returns, the DMA + * transfer is still in progress. + * \param buffer buffer to write + * \param size size of buffer to write + */ + void writeDma(const char *buffer, size_t size); + + /** + * Read from DMA buffer and write data to queue + */ + void IRQreadDma(); + + /** + * Start DMA read + */ + void IRQdmaReadStart(); + + /** + * Stop DMA read + * \return the number of characters in rxBuffer + */ + int IRQdmaReadStop(); + #endif //SERIAL_DMA + + /** + * Wait until all characters have been written to the serial port. + * Needs to be callable from interrupts disabled (it is used in IRQwrite) + */ + void waitSerialTxFifoEmpty() + { + #if !defined(_ARCH_CORTEXM7_STM32F7) && !defined(_ARCH_CORTEXM7_STM32H7) \ + && !defined(_ARCH_CORTEXM0_STM32) && !defined(_ARCH_CORTEXM4_STM32F3) \ + && !defined(_ARCH_CORTEXM4_STM32L4) + while((port->SR & USART_SR_TC)==0) ; + #else //_ARCH_CORTEXM7_STM32F7/H7 + while((port->ISR & USART_ISR_TC)==0) ; + #endif //_ARCH_CORTEXM7_STM32F7/H7 + } + + FastMutex txMutex; ///< Mutex locked during transmission + FastMutex rxMutex; ///< Mutex locked during reception + + DynUnsyncQueue rxQueue; ///< Receiving queue + static const unsigned int rxQueueMin=16; ///< Minimum queue size + Thread *rxWaiting=0; ///< Thread waiting for rx, or 0 + + USART_TypeDef *port; ///< Pointer to USART peripheral + #ifdef SERIAL_DMA + #if defined(_ARCH_CORTEXM3_STM32) || defined(_ARCH_CORTEXM4_STM32F3) \ + || defined(_ARCH_CORTEXM4_STM32L4) + DMA_Channel_TypeDef *dmaTx; ///< Pointer to DMA TX peripheral + DMA_Channel_TypeDef *dmaRx; ///< Pointer to DMA RX peripheral + #else //_ARCH_CORTEXM3_STM32 and _ARCH_CORTEXM4_STM32F3 + DMA_Stream_TypeDef *dmaTx; ///< Pointer to DMA TX peripheral + DMA_Stream_TypeDef *dmaRx; ///< Pointer to DMA RX peripheral + #endif //_ARCH_CORTEXM3_STM32 and _ARCH_CORTEXM4_STM32F3 + Thread *txWaiting; ///< Thread waiting for tx, or 0 + static const unsigned int txBufferSize=16; ///< Size of tx buffer, for tx speedup + /// Tx buffer, for tx speedup. This buffer must not end up in the CCM of the + /// STM32F4, as it is used to perform DMA operations. This is guaranteed by + /// the fact that this class must be allocated on the heap as it derives + /// from Device, and the Miosix linker scripts never put the heap in CCM + char txBuffer[txBufferSize]; + /// This buffer emulates the behaviour of a 16550. It is filled using DMA + /// and an interrupt is fired as soon as it is half full + char rxBuffer[rxQueueMin]; + bool dmaTxInProgress; ///< True if a DMA tx is in progress + #endif //SERIAL_DMA + bool idle=true; ///< Receiver idle + const bool flowControl; ///< True if flow control GPIOs enabled + const unsigned char portId; ///< 1 for USART1, 2 for USART2, ... +}; + +} //namespace miosix + +#endif //SERIAL_STM32_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/servo_stm32.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/servo_stm32.cpp new file mode 100644 index 00000000..518d4a8b --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/servo_stm32.cpp @@ -0,0 +1,356 @@ + /*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "servo_stm32.h" +#include "kernel/scheduler/scheduler.h" +#include +#include +#include + +using namespace std; +using namespace miosix; + +typedef Gpio servo1out; +typedef Gpio servo2out; +typedef Gpio servo3out; +typedef Gpio servo4out; +static Thread *waiting=0; + +/** + * Timer 4 interrupt handler actual implementation + */ +void __attribute__((used)) tim4impl() +{ + TIM4->SR=0; //Clear interrupt flag + if(waiting==0) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * Timer 4 interrupt handler + */ +void __attribute__((naked)) TIM4_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z8tim4implv"); + restoreContext(); +} + +namespace miosix { + +/* TODO: find a better place for this */ +unsigned int divideRoundToNearest(unsigned int a, unsigned int b) +{ + const unsigned int quot=2*a/b; + return quot/2 + (quot & 1); +} + +// +// class SynchronizedServo +// + +SynchronizedServo& SynchronizedServo::instance() +{ + static SynchronizedServo singleton; + return singleton; +} + +void SynchronizedServo::enable(int channel) +{ + Lock l(mutex); + if(status!=STOPPED) return; // If timer enabled ignore the call + + { + FastInterruptDisableLock dLock; + // Calling the mode() function on a GPIO is subject to race conditions + // between threads on the STM32, so we disable interrupts + switch(channel) + { + case 0: + TIM4->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE; + TIM4->CCER |= TIM_CCER_CC1E; + #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it + servo1out::alternateFunction(2); + #endif //_ARCH_CORTEXM3_STM32 + servo1out::mode(Mode::ALTERNATE); + break; + case 1: + TIM4->CCMR1 |= TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; + TIM4->CCER |= TIM_CCER_CC2E; + #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it + servo2out::alternateFunction(2); + #endif //_ARCH_CORTEXM3_STM32 + servo2out::mode(Mode::ALTERNATE); + break; + case 2: + TIM4->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE; + TIM4->CCER |= TIM_CCER_CC3E; + #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it + servo3out::alternateFunction(2); + #endif //_ARCH_CORTEXM3_STM32 + servo3out::mode(Mode::ALTERNATE); + break; + case 3: + TIM4->CCMR2 |= TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE; + TIM4->CCER |= TIM_CCER_CC4E; + #ifndef _ARCH_CORTEXM3_STM32 //Only stm32f2 and stm32f4 have it + servo4out::alternateFunction(2); + #endif //_ARCH_CORTEXM3_STM32 + servo4out::mode(Mode::ALTERNATE); + break; + } + } +} + +void SynchronizedServo::disable(int channel) +{ + Lock l(mutex); + if(status!=STOPPED) return; // If timer enabled ignore the call + + { + FastInterruptDisableLock dLock; + // Calling the mode() function on a GPIO is subject to race conditions + // between threads on the STM32, so we disable interrupts + switch(channel) + { + case 0: + servo1out::mode(Mode::INPUT); + TIM4->CCER &= ~TIM_CCER_CC1E; + break; + case 1: + servo2out::mode(Mode::INPUT); + TIM4->CCER &= ~TIM_CCER_CC2E; + break; + case 2: + servo3out::mode(Mode::INPUT); + TIM4->CCER &= ~TIM_CCER_CC3E; + break; + case 3: + servo4out::mode(Mode::INPUT); + TIM4->CCER &= ~TIM_CCER_CC4E; + break; + } + } +} + +void SynchronizedServo::setPosition(int channel, float value) +{ + Lock l(mutex); + if(status!=STARTED) return; // If timer disabled ignore the call + + value=min(1.0f,max(0.0f,value)); + switch(channel) + { + case 0: + TIM4->CCR1=a*value+b; + break; + case 1: + TIM4->CCR2=a*value+b; + break; + case 2: + TIM4->CCR3=a*value+b; + break; + case 3: + TIM4->CCR4=a*value+b; + break; + } +} + +float SynchronizedServo::getPosition(int channel) +{ + switch(channel) + { + case 0: + return TIM4->CCR1==0 ? NAN : TIM4->CCR1/a-b; + case 1: + return TIM4->CCR2==0 ? NAN : TIM4->CCR2/a-b; + case 2: + return TIM4->CCR3==0 ? NAN : TIM4->CCR3/a-b; + case 3: + return TIM4->CCR4==0 ? NAN : TIM4->CCR4/a-b; + default: + return NAN; + } +} + +void SynchronizedServo::start() +{ + Lock l(mutex); + if(status!=STOPPED) return; // If timer enabled ignore the call + + // While status is starting neither memeber function callable with timer + // started nor stopped are allowed + status=STARTED; + TIM4->CNT=0; + TIM4->EGR=TIM_EGR_UG; + TIM4->CR1=TIM_CR1_CEN; +} + +void SynchronizedServo::stop() +{ + Lock l(mutex); + if(status!=STARTED) return; // If timer disabled ignore the call + + status=STOPPED; + // Stopping the timer is a bit difficult because we don't want to + // accidentally create glitches on the outputs which may turn the servos + // to random positions. So, we set all PWM outputs to 0, then wait until the + // end of the period, and then disable the timer + TIM4->CCR1=0; + TIM4->CCR2=0; + TIM4->CCR3=0; + TIM4->CCR4=0; + { + FastInterruptDisableLock dLock; + // Wakeup an eventual thread waiting on waitForCycleBegin() + if(waiting) waiting->IRQwakeup(); + IRQwaitForTimerOverflow(dLock); + } + TIM4->CR1=0; +} + +bool SynchronizedServo::waitForCycleBegin() +{ + // No need to lock the mutex because disabling interrupts is enough to avoid + // race conditions. Also, locking the mutex here would prevent other threads + // from calling other member functions of this class + FastInterruptDisableLock dLock; + if(status!=STARTED) return true; + IRQwaitForTimerOverflow(dLock); + return status!=STARTED; +} + +void SynchronizedServo::setFrequency(unsigned int frequency) +{ + Lock l(mutex); + if(status!=STOPPED) return; // If timer enabled ignore the call + + TIM4->PSC=divideRoundToNearest(getPrescalerInputFrequency(),frequency*65536)-1; + precomputeCoefficients(); +} + +float SynchronizedServo::getFrequency() const +{ + float prescalerFreq=getPrescalerInputFrequency(); + return prescalerFreq/((TIM4->PSC+1)*65536); +} + +void SynchronizedServo::setMinPulseWidth(float minPulse) +{ + Lock l(mutex); + if(status!=STOPPED) return; // If timer enabled ignore the call + + minWidth=1e-6f*min(1300.0f,max(500.0f,minPulse)); + precomputeCoefficients(); +} + +void SynchronizedServo::setMaxPulseWidth(float maxPulse) +{ + Lock l(mutex); + if(status!=STOPPED) return; // If timer enabled ignore the call + + maxWidth=1e-6f*min(2500.0f,max(1700.0f,maxPulse)); + precomputeCoefficients(); +} + +SynchronizedServo::SynchronizedServo() : status(STOPPED) +{ + { + FastInterruptDisableLock dLock; + // The RCC register should be written with interrupts disabled to + // prevent race conditions with other threads. + RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; + RCC_SYNC(); + } + + // Configure timer + TIM4->CR1=0; + TIM4->ARR=0xffff; + TIM4->CCR1=0; + TIM4->CCR2=0; + TIM4->CCR3=0; + TIM4->CCR4=0; + // Configure interrupt on timer overflow + TIM4->DIER=TIM_DIER_UIE; + NVIC_SetPriority(TIM4_IRQn,13); //Low priority for timer IRQ + NVIC_EnableIRQ(TIM4_IRQn); + // Set default parameters + setFrequency(50); + setMinPulseWidth(1000); + setMaxPulseWidth(2000); +} + +void SynchronizedServo::precomputeCoefficients() +{ + float realPeriod=1.0f/getFrequency(); + a=65536.0f*(maxWidth-minWidth)/realPeriod; + b=65536.0f*minWidth/realPeriod; +} + +unsigned int SynchronizedServo::getPrescalerInputFrequency() +{ + // The global variable SystemCoreClock from ARM's CMSIS allows to know + // the CPU frequency. + unsigned int freq=SystemCoreClock; + + //The position of the PPRE1 bit in RCC->CFGR is different in some stm32 + #ifdef _ARCH_CORTEXM3_STM32 + const unsigned int ppre1=8; + #else //stm32f2 and f4 + const unsigned int ppre1=10; + #endif + + // The timer frequency may however be a submultiple of the CPU frequency, + // due to the bus at whch the periheral is connected being slower. The + // RCC->CFGR register tells us how slower the APB1 bus is running. + // This formula takes into account that if the APB1 clock is divided by a + // factor of two or greater, the timer is clocked at twice the bus + // interface. After this, the freq variable contains the frequency in Hz + // at which the timer prescaler is clocked. + if(RCC->CFGR & RCC_CFGR_PPRE1_2) freq/=1<<((RCC->CFGR>>ppre1) & 0x3); + + return freq; +} + +void SynchronizedServo::IRQwaitForTimerOverflow(FastInterruptDisableLock& dLock) +{ + waiting=Thread::IRQgetCurrentThread(); + do { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(waiting); +} + +} //namespace miosix + + diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/servo_stm32.h b/lib/miosix-kernel/miosix/arch/common/drivers/servo_stm32.h new file mode 100644 index 00000000..a3570d3c --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/servo_stm32.h @@ -0,0 +1,196 @@ +/*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SERVO_STM32_H +#define SERVO_STM32_H + +#include "miosix.h" + +namespace miosix { + +/** + * This class is designed to drive up to 4 servomotors. It generates + * four square waves that are synchronized with respect to each other, + * and allows the execution of code that is too synchronized with the + * waveform generation, to ease the development of closed loop control + * code using the servos as actuators. + * This class can be safely accessed by multiple threads, except the + * waitForCycleBegin() member function. + */ +class SynchronizedServo +{ +public: + /** + * \return an instance of the SynchronizedServo class (singleton) + * When first returned, the servo waveform generation is stopped. You must + * enable at least one channel call start() and setPosition() before the + * servo driving waveforms will be generated. + */ + static SynchronizedServo& instance(); + + /** + * Enable a channel. Can only be called with the outputs stopped. Even if + * the channel is enabled, when it is started it will not produce any + * waveform until the first call to setPosition() + * \param channel which channel to enable, must be between 0 and 3. + */ + void enable(int channel); + + /** + * Disable a channel. Can only be called with the outputs stopped. + * \param channel which channel to disable, must be between 0 and 3. + */ + void disable(int channel); + + /** + * Set the servo position. Can only be called with the outputs started. + * The written value takes effect at the next waveform generation cycle. + * \param channel channel whose output need to be changed, between 0 and 3 + * \param value a float value from 0.0 to 1.0. Due to the limited timer + * resolution, the actual set value is approximated. With the default values + * of waveform frequency, min and max width the range between 0.0 and 1.0 + * is covered by around 3200 points. + */ + void setPosition(int channel, float value); + + /** + * \param channel channel whose output need to be read, between 0 and 3 + * \return the exact servo position, considering the approximations. + * For this reason, the returned value may differ from the last call to + * setPosition(). NAN is returned if no setValue() was called on the channel + * since the last call to start() + */ + float getPosition(int channel); + + /** + * Start producing the output waveforms. + */ + void start(); + + /** + * Stop producing the output waveforms. If a thread is waiting in + * waitForCycleBegin() it will be forecefully woken up. + * As a side effect, the position of all the channels will be set to NAN. + * When you restart the timer, you must call setPosition() on each enabled + * channel before the channel will actually produce a waveform. + * This function waits until the end of a waveform generation cycle in order + * to not produce glitches. For this reason, it may take up to + * 1/getFrequency() to complete, which with the default value of 50Hz is 20ms + */ + void stop(); + + /** + * Wait until the begin of a waveform generation cycle + * \return false if a new cycle of waveform generation has begun, or true + * if another thread called stop(). Only one thread at a time can call this + * member function. If more than one thread calls this deadlock will occur + * so don't do it! + */ + bool waitForCycleBegin(); + + /** + * Set the frequency of the generated waveform. Can only be called + * with the outputs stopped. The default is 50Hz. Note that due to prescaler + * resolution, the actual frequency is set to the closest possible value. + * To know the actual frequency, call getFrequency() + * \param frequency desired servo update frequency in Hz + * Must be between 10 and 100Hz + */ + void setFrequency(unsigned int frequency); + + /** + * \return the actual servo update frequency in Hz. Note that the + * returned value is floating point as the returned frequency takes into + * account approximations due to the prescaler resolution + */ + float getFrequency() const; + + /** + * Set the minimum width of the generated pulses, that is, the pulse width + * generated when an output is set to zero with setPosition(x,0). + * The default is 1000us. Can only be called with the outputs stopped. + * \param minPulse minimum pulse width in microseconds. + * Must be between 500 and 1300. + */ + void setMinPulseWidth(float minPulse); + + /** + * \return minimum pulse width in microseconds + */ + float getMinPulseWidth() const { return minWidth*1e6f; } + + /** + * Set the maximum width of the generated pulses, that is, the pulse width + * generated when an output is set to one with setPosition(x,1). + * The default is 2000us. Can only be called with the outputs stopped. + * \param maxPulse maximum pulse width in microseconds. + * Must be between 1700 and 2500. + */ + void setMaxPulseWidth(float maxPulse); + + /** + * \return maximum pulse width in microseconds + */ + float getMaxPulseWidth() const { return maxWidth*1e6f; } + +private: + SynchronizedServo(const SynchronizedServo&); + SynchronizedServo& operator= (const SynchronizedServo&); + + /** + * Constructor + */ + SynchronizedServo(); + + /** + * Precompute a and b coefficient to make setPosition() faster + */ + void precomputeCoefficients(); + + /** + * \return the input frequency of the timer prescaler + */ + static unsigned int getPrescalerInputFrequency(); + + /** + * Wait until the timer overflows from 0xffff to 0. Can only be called with + * interrupts disabled + */ + static void IRQwaitForTimerOverflow(FastInterruptDisableLock& dLock); + + float minWidth, maxWidth; ///< Minimum and maximum pulse widths + float a, b; ///< Precomputed coefficients + FastMutex mutex; ///< Mutex to protect from concurrent access + enum { + STOPPED, ///< Timer is stopped + STARTED ///< Timer is started + } status; +}; + +} //namespace miosix + +#endif //SERVO_STM32_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_hardware_rng.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_hardware_rng.cpp new file mode 100644 index 00000000..9595e24d --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_hardware_rng.cpp @@ -0,0 +1,121 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include "interfaces/delays.h" +#include "stm32_hardware_rng.h" + +using namespace std; + +namespace miosix { + +// +// class HardwareRng +// + +HardwareRng& HardwareRng::instance() +{ + static HardwareRng singleton; + return singleton; +} + +unsigned int HardwareRng::get() +{ + miosix::Lock l(mutex); + PeripheralEnable pe; + return getImpl(); +} + +void HardwareRng::get(void* buf, unsigned int size) +{ + unsigned char *buffer=reinterpret_cast(buf); + Lock l(mutex); + PeripheralEnable pe; + union Cast + { + unsigned int theInt; + unsigned char theChar[4]; + }; + + if(reinterpret_cast(buffer) & 0x3) + { + Cast cast; + cast.theInt=getImpl(); + int i=0; + while(reinterpret_cast(buffer) & 0x3) + { + if(size--==0) return; //May happen if buffer is small and unaligned + *buffer++=cast.theChar[i++]; + } + } + + unsigned int *aligned=reinterpret_cast(buffer); + for(unsigned int i=0;i0) { sr=RNG->SR; if(sr & RNG_SR_DRDY) break; } + if((sr & RNG_SR_SECS) || (sr & RNG_SR_CECS) || timeout<=0) + { + RNG->CR=0; + delayUs(1); + RNG->CR=RNG_CR_RNGEN; + continue; + } + unsigned int result=RNG->DR; + if(result==old) continue; + old=result; + return result; + } + #ifndef __NO_EXCEPTIONS + throw runtime_error("RNG Fault detected"); + #endif //__NO_EXCEPTIONS +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_hardware_rng.h b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_hardware_rng.h new file mode 100644 index 00000000..ceab1df0 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_hardware_rng.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef RNG_H +#define RNG_H + +#include "interfaces/arch_registers.h" +#include "kernel/sync.h" + +namespace miosix { + +/** + * Class to access the hardware random number generator in Miosix + * Works with the hardware RNG in stm32f2 and stm32f4 + */ +class HardwareRng +{ +public: + /** + * \return an instance of this class (singleton) + */ + static HardwareRng& instance(); + + /** + * \return a 32 bit random number + * \throws runtime_error if the self test is not passed + */ + unsigned int get(); + + /** + * Fill a buffer with random data + * \param buf buffer to be filled + * \param size buffer size + * \throws runtime_error if the self test is not passed + */ + void get(void *buf, unsigned int size); + +private: + HardwareRng(const HardwareRng&); + HardwareRng& operator=(const HardwareRng&); + + /** + * Constructor + */ + HardwareRng() : old(0) + { + miosix::FastInterruptDisableLock dLock; + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; + RCC_SYNC(); + } + + /** + * \return a 32 bit random number + * \throws runtime_error if the self test is not passed + */ + unsigned int getImpl(); + + /** + * To save power, enable the peripheral ony when requested + */ + class PeripheralEnable + { + public: + PeripheralEnable() { RNG->CR=RNG_CR_RNGEN; } + ~PeripheralEnable() { RNG->CR=0; } + }; + + miosix::FastMutex mutex; ///< To protect against concurrent access + unsigned int old; ///< Previously read value +}; + +} //namespace miosix + +#endif //RNG_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.cpp new file mode 100644 index 00000000..13dd332c --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.cpp @@ -0,0 +1,322 @@ +/*************************************************************************** + * Copyright (C) 2017 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "stm32_rtc.h" +#include +#include +#include + +using namespace miosix; + +namespace { + +// +// class ScopedCnf, RAII to allow writing to the RTC alarm and prescaler. +// NOTE: datasheet says that after CNF bit has been cleared, no write is allowed +// to *any* of the RTC registers, not just PRL,CNT,ALR until RTOFF goes to 1. +// We do not wait until RTOFF is 1 in the destructor for performance reasons, +// so the rest of the code must be careful. +// +class ScopedCnf +{ +public: + ScopedCnf() + { + while((RTC->CRL & RTC_CRL_RTOFF)==0) ; + RTC->CRL=0b11111; + } + + ~ScopedCnf() + { + RTC->CRL=0b01111; + } +}; + +long long swTime=0; //64bit software extension of current time in ticks +long long irqTime=0; //64bit software extension of scheduled irq time in ticks +Thread *waiting=nullptr; //waiting thread + +/** + * \return the hardware counter + */ +inline unsigned int IRQgetHwTick() +{ + unsigned int h1=RTC->CNTH; + unsigned int l1=RTC->CNTL; + unsigned int h2=RTC->CNTH; + if(h1==h2) return (h1<<16) | l1; + return (h2<<16) | RTC->CNTL; +} + +/** + * \return the time in ticks (hardware part + software extension to 64bits) + */ +long long IRQgetTick() +{ + //Pending bit trick + unsigned int hwTime=IRQgetHwTick(); + if((RTC->CRL & RTC_CRL_OWF) && IRQgetHwTick()>=hwTime) + return (swTime + static_cast(hwTime)) + (1LL<<32); + return swTime + static_cast(hwTime); +} + +/** + * Sleep the current thread till the specified time + * \param tick absolute time in ticks + * \param dLock used to reenable interrupts while sleeping + * \return true if the wait time was in the past + */ +bool IRQabsoluteWaitTick(long long tick, FastInterruptDisableLock& dLock) +{ + irqTime=tick; + unsigned int hwAlarm=(tick & 0xffffffffULL) - (swTime & 0xffffffffULL); + { + ScopedCnf cnf; + RTC->ALRL=hwAlarm; + RTC->ALRH=hwAlarm>>16; + } + //NOTE: We do not wait for the alarm register to be written back to the low + //frequency domain for performance reasons. The datasheet says it takes + //at least 3 cycles of the 32KHz clock, but experiments show that it takes + //from 2 to 3, so perhaps they meant "at most 3". Because of this we + //consider the time in the past if we are more than 2 ticks of the 16KHz + //clock (4 ticks of the 32KHz one) in advance. Sleeps less than 122us are + //thus not supported. + if(IRQgetTick()>=tick-2) return true; + waiting=Thread::IRQgetCurrentThread(); + do { + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } while(waiting); + return false; +} + +} //anon namespace + +/** + * RTC interrupt + */ +void __attribute__((naked)) RTC_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z10RTCIrqImplv"); + restoreContext(); +} + +/** + * RTC interrupt actual implementation + */ +void __attribute__((used)) RTCIrqImpl() +{ + unsigned int crl=RTC->CRL; + if(crl & RTC_CRL_OWF) + { + RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_OWF; + swTime+=1LL<<32; + } else if(crl & RTC_CRL_ALRF) { + RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_ALRF; + if(waiting && IRQgetTick()>=irqTime) + { + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()> + Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=nullptr; + } + } +} + +namespace miosix { + +void absoluteDeepSleep(long long int value) +{ + #ifdef RUN_WITH_HSI + const int wakeupAdvance=3; //waking up takes time + #else //RUN_WITH_HSI + const int wakeupAdvance=33; //HSE starup time is 2ms + #endif //RUN_WITH_HSI + + Rtc& rtc=Rtc::instance(); + ioctl(STDOUT_FILENO,IOCTL_SYNC,0); + + FastInterruptDisableLock dLock; + + //Set alarm and enable EXTI + long long wkupTick=rtc.tc.ns2tick(value)-wakeupAdvance; + unsigned int hwAlarm=(wkupTick & 0xffffffffULL) - (swTime & 0xffffffffULL); + { + ScopedCnf cnf; + RTC->ALRL=hwAlarm; + RTC->ALRH=hwAlarm>>16; + } + while((RTC->CRL & RTC_CRL_RTOFF)==0) ; + + + EXTI->RTSR |= EXTI_RTSR_TR17; + EXTI->EMR |= EXTI_EMR_MR17; //enable event for wakeup + + long long tick=IRQgetTick(); + while(tickCR |= PWR_CR_LPDS; + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + // Using WFE instead of WFI because if while we are with interrupts + // disabled an interrupt (such as the tick interrupt) occurs, it + // remains pending and the WFI becomes a nop, and the device never goes + // in sleep mode. WFE events are latched in a separate pending register + // so interrupts do not interfere with them + __WFE(); + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + PWR->CR &= ~PWR_CR_LPDS; + + #ifndef SYSCLK_FREQ_24MHz + #error TODO: support more PLL frequencies + #endif + //STOP mode resets the clock to the HSI 8MHz, so restore the 24MHz clock + #ifndef RUN_WITH_HSI + RCC->CR |= RCC_CR_HSEON; + while((RCC->CR & RCC_CR_HSERDY)==0) ; + //PLL = (HSE / 2) * 6 = 24 MHz + RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL); + RCC->CFGR |= RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL6; + #else //RUN_WITH_HSI + //PLL = (HSI / 2) * 6 = 24 MHz + RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL); + RCC->CFGR |= RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL6; + #endif //RUN_WITH_HSI + RCC->CR |= RCC_CR_PLLON; + while((RCC->CR & RCC_CR_PLLRDY)==0) ; + RCC->CFGR &= ~RCC_CFGR_SW; + RCC->CFGR |= RCC_CFGR_SW_PLL; + while((RCC->CFGR & RCC_CFGR_SWS)!=0x08) ; + + //Wait RSF + RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_RSF; + while((RTC->CRL & RTC_CRL_RSF)==0) ; + + //Clear IRQ + unsigned int crl=RTC->CRL; + //NOTE: ST docs say OWF, ALRF are not updated in deep sleep. So, to + //detect counter ovreflow we check for oldTick>newTick. The check for + //flags is still there in case overflow/alarm occurs before/after sleep + if(crl & RTC_CRL_OWF || tick>IRQgetTick()) + { + RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_OWF; + swTime+=1LL<<32; + } else if(crl & RTC_CRL_ALRF) { + RTC->CRL=(RTC->CRL | 0xf) & ~RTC_CRL_ALRF; + } + + //Update tick, done after checking IRQ flags to avoid race condition + tick=IRQgetTick(); + } + + EXTI->EMR &=~ EXTI_EMR_MR17; //disable event for wakeup + + wkupTick+=wakeupAdvance; + + //NOTE: if we use the HSE we may spin for a while, but adding a + //IRQabsoluteWaitTick here makes this function wakeup too late + while(IRQgetTick()APB1ENR |= RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; + PWR->CR |= PWR_CR_DBP; + RCC->BDCR=RCC_BDCR_RTCEN //RTC enabled + | RCC_BDCR_LSEON //External 32KHz oscillator enabled + | RCC_BDCR_RTCSEL_0; //Select LSE as clock source for RTC + RCC_SYNC(); + #ifdef RTC_CLKOUT_ENABLE + BKP->RTCCR=BKP_RTCCR_CCO; //Output RTC clock/64 on pin + #endif + } + while((RCC->BDCR & RCC_BDCR_LSERDY)==0) ; //Wait for LSE to start + + RTC->CRH=RTC_CRH_OWIE | RTC_CRH_ALRIE; + { + ScopedCnf cnf; + RTC->PRLH=0; + RTC->PRLL=1; + } + NVIC_SetPriority(RTC_IRQn,5); + NVIC_EnableIRQ(RTC_IRQn); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.h b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.h new file mode 100644 index 00000000..de080010 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_rtc.h @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (C) 2017 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef RTC_H +#define RTC_H + +#include + +namespace miosix { + +/// +/// Uncomment this directive to enable the RTC clock output functionality +/// + +//#define RTC_CLKOUT_ENABLE + +/** + * Puts the MCU in deep sleep until the specified absolute time. + * \param value absolute wait time in nanoseconds + * If value of absolute time is in the past no waiting will be set + * and function return immediately. + */ +void absoluteDeepSleep(long long value); + +/** + * Driver for the stm32 RTC. + * All the wait and deepSleep functions cannot be called concurrently by + * multiple threads. + */ +class Rtc +{ +public: + /** + * \return an instance of this class + */ + static Rtc& instance(); + + /** + * \return the timer counter value in nanoseconds + */ + long long getValue() const; + + /** + * \return the timer counter value in nanoseconds + * + * Can be called with interrupt disabled, or inside an interrupt + */ + long long IRQgetValue() const; + + /** + * Set the timer counter value + * \param value new timer value in nanoseconds + * + * NOTE: if alarm is set wakeup time is not updated + */ +// void setValue(long long value); + + /** + * Put thread in wait for the specified relative time. + * This function wait for a relative time passed as parameter. + * \param value relative time to wait, expressed in nanoseconds + */ + void wait(long long value); + + /** + * Puts the thread in wait until the specified absolute time. + * \param value absolute wait time in nanoseconds + * If value of absolute time is in the past no waiting will be set + * and function return immediately. + * \return true if the wait time was in the past + */ + bool absoluteWait(long long value); + + /** + * \return the timer frequency in Hz + */ + unsigned int getTickFrequency() const { return 16384; } + +private: + Rtc(); + Rtc(const Rtc&)=delete; + Rtc& operator= (const Rtc&)=delete; + + TimeConversion tc; + + friend void absoluteDeepSleep(long long value); +}; + +} //namespace miosix + +#endif //RTC_H diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_sgm.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_sgm.cpp new file mode 100644 index 00000000..64849679 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_sgm.cpp @@ -0,0 +1,130 @@ +/*************************************************************************** + * Copyright (C) 2017 by Matteo Michele Piazzolla * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "board_settings.h" +#include "stm32_sgm.h" +#include +#include "miosix.h" + +namespace miosix { + +extern unsigned char _preserve_start asm("_preserve_start"); +extern unsigned char _preserve_end asm("_preserve_end"); + +static unsigned char *preserve_start=&_preserve_start; +static unsigned char *preserve_end=&_preserve_end; + +SGM& SGM::instance() +{ + static SGM singleton; + return singleton; +} + +SGM::SGM() +{ + /* Enable PWR clock */ + RCC->APB1ENR |= RCC_APB1ENR_PWREN; + + /* Enable backup SRAM Clock */ + RCC->AHB1ENR |= RCC_AHB1ENR_BKPSRAMEN; + + enableWrite(); + + /* Enable Backup regulator */ + PWR->CSR |= PWR_CSR_BRE; + + /* Enable the Backup SRAM low power Regulator */ + PWR->CSR |= PWR_CSR_BRE; + + /* Wait for backup regulator */ + while (!(PWR->CSR & (PWR_CSR_BRR))); + + /* Retrive last reset reason and clear the pending flag */ + readResetRegister(); + + /* + * If the reset wasn't caused by software failure we cannot trust + * the content of the backup memory and we need to clear it. + */ + if(lastReset != RST_SW) + { + memset(preserve_start, 0, preserve_end-preserve_start); + } +} + +void SGM::disableWrite() +{ + /* Enable Backup Domain write protection */ + PWR->CR &= ~PWR_CR_DBP; +} + +void SGM::enableWrite() +{ + /* Disable Backup Domain write protection */ + PWR->CR |= PWR_CR_DBP; +} + +void SGM::clearResetFlag() +{ + RCC->CSR |= RCC_CSR_RMVF; +} + +void SGM::readResetRegister() +{ + uint32_t resetReg = RCC->CSR; + clearResetFlag(); + if(resetReg & RCC_CSR_LPWRRSTF) + { + lastReset = RST_LOW_PWR; + } + else if( resetReg & RCC_CSR_WWDGRSTF) + { + lastReset = RST_WINDOW_WDG; + } + else if( resetReg & RCC_CSR_WDGRSTF) + { + lastReset = RST_INDEPENDENT_WDG; + } + else if( resetReg & RCC_CSR_SFTRSTF) + { + lastReset = RST_SW; + } + else if( resetReg & RCC_CSR_PORRSTF) + { + lastReset = RST_POWER_ON; + } + else if( resetReg & RCC_CSR_PADRSTF) + { + lastReset = RST_PIN; + } + else + { + lastReset = RST_UNKNOWN; + } +} + +} // namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_sgm.h b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_sgm.h new file mode 100644 index 00000000..10cc6dc9 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_sgm.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2017 by Matteo Michele Piazzolla * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "board_settings.h" + +#define PRESERVE __attribute__((section(".preserve"))) + +namespace miosix { + +/** + * Possible causes for an STM32 reset + */ +enum ResetReason +{ + RST_LOW_PWR=0, + RST_WINDOW_WDG=1, + RST_INDEPENDENT_WDG=2, + RST_SW=3, + RST_POWER_ON=4, + RST_PIN=5, + RST_UNKNOWN=6, +}; + +/** + * Driver for the STM32F2 and STM32F4 backup SRAM, here used as + * SafeGuard Memory, that is, a memory whose value is preseved across resets. + */ +class SGM +{ +public: + /** + * \return an instance of this class (singleton) + */ + static SGM& instance(); + + /** + * Temporarily disable writing to the safeguard memory. + * By deafult, from reset to when the contrsuctor of this class is called + * the safeguard memory is not writable. After the constructor is called, + * the safeguard memory is writable. + */ + void disableWrite(); + + /** + * Make the safeguard memory writable again, after a call to disableWrite() + */ + void enableWrite(); + + /** + * Return the cause of the last reset of the microcontroller + */ + ResetReason lastResetReason() { return lastReset; } + +private: + ResetReason lastReset; + + SGM(const SGM&)=delete; + SGM& operator=(const SGM&)=delete; + + SGM(); + void readResetRegister(); + void clearResetFlag(); + +}; + + +} \ No newline at end of file diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_wd.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_wd.cpp new file mode 100644 index 00000000..8e323c72 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_wd.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2017 by Matteo Michele Piazzolla * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "stm32_wd.h" +#include "miosix.h" +#include + +using namespace std; + +namespace miosix { + +IWatchDog& IWatchDog::instance() +{ + static IWatchDog singleton; + return singleton; +} + +void IWatchDog::enable(int ms) +{ + /* disable WD configuration protection */ + IWDG->KR = 0x5555; + + /* set prescaler divider to /32 */ + IWDG->PR = 0x3; + + /* set reload register */ + IWDG->RLR = max(1,min(4096,ms))-1; + + /* start the watchdog */ + IWDG->KR = 0xCCCC; +} + +void IWatchDog::refresh() +{ + IWDG->KR=0xAAAA; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32_wd.h b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_wd.h new file mode 100644 index 00000000..199bf54f --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32_wd.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2017 by Matteo Michele Piazzolla * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +namespace miosix { + +/** + * Driver for the STM32 independent watchdog + */ +class IWatchDog +{ +public: + /** + * \return an instance of this class (singleton) + */ + static IWatchDog& instance(); + + /** + * Enable the watchdog + * \param ms reload period, from 1 (1 millisecond) to 4096 (4.096 seconds) + */ + void enable(int ms); + + /** + * If the watchdog is not periodically reloaded at least within the + * period selected by enable, the microcontroller is reset. + * The datsheet says that the oscillator clocking the watchdog can be + * up to twice as fast, so it is recomended to reload the watchdog three + * to four times faster to prevent spurious resets. + */ + void refresh(); + +private: + IWatchDog(const IWatchDog&)=delete; + IWatchDog& operator=(const IWatchDog&)=delete; + IWatchDog() {} +}; + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32f2_f4_i2c.cpp b/lib/miosix-kernel/miosix/arch/common/drivers/stm32f2_f4_i2c.cpp new file mode 100644 index 00000000..69067f9a --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32f2_f4_i2c.cpp @@ -0,0 +1,476 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico and Silvano Seva * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "stm32f2_f4_i2c.h" +#include +#include + +using namespace miosix; + +static volatile bool error; ///< Set to true by IRQ on error +static Thread *waiting=0; ///< Thread waiting for an operation to complete + +/* In non-DMA mode the variables below are used to + * handle the reception of 2 or more bytes through + * an interrupt, avoiding the thread that calls recv + * to be locked in polling + */ + +#ifndef I2C_WITH_DMA +static uint8_t *rxBuf = 0; +static unsigned int rxBufCnt = 0; +static unsigned int rxBufSize = 0; +#endif + + +#ifdef I2C_WITH_DMA +/** + * DMA I2C rx end of transfer + */ +void __attribute__((naked)) DMA1_Stream0_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z20I2C1rxDmaHandlerImplv"); + restoreContext(); +} + +/** + * DMA I2C rx end of transfer actual implementation + */ +void __attribute__((used)) I2C1rxDmaHandlerImpl() +{ + DMA1->LIFCR=DMA_LIFCR_CTCIF0 + | DMA_LIFCR_CTEIF0 + | DMA_LIFCR_CDMEIF0 + | DMA_LIFCR_CFEIF0; + I2C1->CR1 |= I2C_CR1_STOP; + if(waiting==0) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * DMA I2C tx end of transfer + */ +void DMA1_Stream7_IRQHandler() +{ + DMA1->HIFCR=DMA_HIFCR_CTCIF7 + | DMA_HIFCR_CTEIF7 + | DMA_HIFCR_CDMEIF7 + | DMA_HIFCR_CFEIF7; + + //We can't just wake the thread because the I2C is double buffered, and this + //interrupt is fired at the same time as the second last byte is starting + //to be sent out of the bus. If we return now, the main code would send a + //stop condiotion too soon, and the last byte would never be sent. Instead, + //we change from DMA mode to IRQ mode, so when the second last byte is sent, + //that interrupt is fired and the last byte is sent out. + //Note that since no thread is awakened from this IRQ, there's no need for + //the saveContext(), restoreContext() and __attribute__((naked)) + I2C1->CR2 &= ~I2C_CR2_DMAEN; + I2C1->CR2 |= I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN; +} + +#endif + +/** + * I2C address sent interrupt + */ +void __attribute__((naked)) I2C1_EV_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z15I2C1HandlerImplv"); + restoreContext(); +} + +/** + * I2C address sent interrupt actual implementation + */ +void __attribute__((used)) I2C1HandlerImpl() +{ + #ifdef I2C_WITH_DMA + //When called to resolve the last byte not sent issue, clearing + //I2C_CR2_ITBUFEN prevents this interrupt being re-entered forever, as + //it does not send another byte to the I2C, so the interrupt would remain + //pending. When called after the start bit has been sent, clearing + //I2C_CR2_ITEVTEN prevents the same infinite re-enter as this interrupt + //does not start an address transmission, which is necessary to stop + //this interrupt from being pending + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN); + if(waiting==0) return; + #else + + bool rxFinished = false; + + /* If rxBuf is equal to zero means that we are sending the slave + address and this ISR is used to manage the address sent interrupt */ + + if(rxBuf == 0) + { + I2C1->CR2 &= ~I2C_CR2_ITEVTEN; + rxFinished = true; + } + + if(I2C1->SR1 & I2C_SR1_RXNE) + { + rxBuf[rxBufCnt++] = I2C1->DR; + if(rxBufCnt >= rxBufSize) + { + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN); + rxFinished = true; + } + } + + if(waiting==0 || !rxFinished) return; + #endif + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +/** + * I2C error interrupt + */ +void __attribute__((naked)) I2C1_ER_IRQHandler() +{ + saveContext(); + asm volatile("bl _Z18I2C1errHandlerImplv"); + restoreContext(); +} + +/** + * I2C error interrupt actual implementation + */ +void __attribute__((used)) I2C1errHandlerImpl() +{ + I2C1->SR1=0; //Clear error flags + error=true; + if(waiting==0) return; + waiting->IRQwakeup(); + if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + waiting=0; +} + +namespace miosix { + +// +// class I2C +// + +I2C1Driver& I2C1Driver::instance() +{ + static I2C1Driver singleton; + return singleton; +} + +void I2C1Driver::init() +{ + //I2C devices are connected to APB1, whose frequency is the system clock + //divided by a value set in the PPRE1 bits of RCC->CFGR + const int ppre1=(RCC->CFGR & RCC_CFGR_PPRE1)>>10; + const int divFactor= (ppre1 & 1<<2) ? (2<<(ppre1 & 0x3)) : 1; + const int fpclk1=SystemCoreClock/divFactor; + //iprintf("fpclk1=%d\n",fpclk1); + + { + FastInterruptDisableLock dLock; + + #ifdef I2C_WITH_DMA + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + RCC_SYNC(); + #endif + RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; //Enable clock gating + RCC_SYNC(); + } + + #ifdef I2C_WITH_DMA + NVIC_SetPriority(DMA1_Stream7_IRQn,10);//Low priority for DMA + NVIC_ClearPendingIRQ(DMA1_Stream7_IRQn);//DMA1 stream 7 channel 1 = I2C1 TX + NVIC_EnableIRQ(DMA1_Stream7_IRQn); + + NVIC_SetPriority(DMA1_Stream0_IRQn,10);//Low priority for DMA + NVIC_ClearPendingIRQ(DMA1_Stream0_IRQn);//DMA1 stream 0 channel 1 = I2C1 RX + NVIC_EnableIRQ(DMA1_Stream0_IRQn); + #endif + + NVIC_SetPriority(I2C1_EV_IRQn,10);//Low priority for I2C + NVIC_ClearPendingIRQ(I2C1_EV_IRQn); + NVIC_EnableIRQ(I2C1_EV_IRQn); + + NVIC_SetPriority(I2C1_ER_IRQn,10); + NVIC_ClearPendingIRQ(I2C1_ER_IRQn); + NVIC_EnableIRQ(I2C1_ER_IRQn); + + I2C1->CR1=I2C_CR1_SWRST; + I2C1->CR1=0; + I2C1->CR2=fpclk1/1000000; //Set pclk frequency in MHz + //This sets the duration of both Thigh and Tlow (master mode)) + const int i2cSpeed=100000; //100KHz + I2C1->CCR=std::max(4,fpclk1/(2*i2cSpeed)); //Duty=2, standard mode (100KHz) + //Datasheet says with I2C @ 100KHz, maximum SCL rise time is 1000ns + //Need to change formula if I2C needs to run @ 400kHz + I2C1->TRISE=fpclk1/1000000+1; + I2C1->CR1=I2C_CR1_PE; //Enable peripheral +} + +bool I2C1Driver::send(unsigned char address, + const void *data, int len, bool sendStop) +{ + address &= 0xfe; //Mask bit 0, as we are writing + if(start(address)==false || (I2C1->SR2 & I2C_SR2_TRA)==0) + { + I2C1->CR1 |= I2C_CR1_STOP; + return false; + } + + error=false; + + #ifdef I2C_WITH_DMA + waiting=Thread::getCurrentThread(); + DMA1_Stream7->CR=0; + DMA1_Stream7->PAR=reinterpret_cast(&I2C1->DR); + DMA1_Stream7->M0AR=reinterpret_cast(data); + DMA1_Stream7->NDTR=len; + DMA1_Stream7->FCR=DMA_SxFCR_FEIE + | DMA_SxFCR_DMDIS; + DMA1_Stream7->CR=DMA_SxCR_CHSEL_0 //Channel 1 + | DMA_SxCR_MINC //Increment memory pointer + | DMA_SxCR_DIR_0 //Memory to peripheral + | DMA_SxCR_TCIE //Interrupt on transfer complete + | DMA_SxCR_TEIE //Interrupt on transfer error + | DMA_SxCR_DMEIE //Interrupt on direct mode error + | DMA_SxCR_EN; //Start DMA + + //Enable DMA in the I2C peripheral *after* having configured the DMA + //peripheral, or a spurious interrupt is triggered + I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_ITERREN; + + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + + DMA1_Stream7->CR=0; + + //The DMA interrupt routine changes the interrupt flags! + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); + #else + + I2C1->CR2 |= I2C_CR2_ITERREN; + + const uint8_t *txData = reinterpret_cast(data); + for(int i=0; iDR = txData[i]; + while(!(I2C1->SR1 & I2C_SR1_TXE)) ; + } + + I2C1->CR2 &= ~I2C_CR2_ITERREN; + #endif + + /* + * The main idea of this driver is to avoid having the processor spinning + * waiting on some status flag. Why? Because I2C is slow compared to a + * modern processor. A 120MHz core does 1200 clock cycles in the time it + * takes to transfer a single bit through an I2C clocked at 100KHz. + * This time could be better spent doing a context switch and letting + * another thread do useful work, or (and Miosix does it automatically if + * there are no ready threads) sleeping the processor core. However, + * I'm quite disappointed by the STM32 I2C peripheral, as it seems overly + * complicated to use. To come close to achieving this goal I had to + * orchestrate among *four* interrupt handlers, two of the DMA, and two + * of the I2C itself. And in the end, what's even more disappointing, is + * that I haven't found a way to completely avoid spinning. Why? + * There's no interrupt that's fired when the stop bit is sent! + * And what's worse, the documentation says that after you set the stop + * bit in the CR2 register you can't write to it again (for example, to send + * a start bit because two i2c api calls are made back to back) until the + * MSL bit is cleared. But there's no interrupt tied to that event! + * What's worse, is that the closest interrupt flag I've found when doing + * an I2C send is fired when the last byte is *beginning* to be sent. + * Maybe I haven't searched well enough, but the fact is I found nothing, + * so this code below spins for 8 data bits of the last byte plus the ack + * bit, plus the stop bit. That's 12000 wasted CPU cycles. Thanks, ST... + */ + + if(sendStop) + { + I2C1->CR1 |= I2C_CR1_STOP; + while(I2C1->SR2 & I2C_SR2_MSL) ; //Wait for stop bit sent + } else { + // Dummy write, is the only way to clear + // the TxE flag if stop bit is not sent... + I2C1->DR = 0x00; + } + return !error; +} + +bool I2C1Driver::recv(unsigned char address, void *data, int len) +{ + address |= 0x01; + if(start(address,len==1)==false || I2C1->SR2 & I2C_SR2_TRA) + { + I2C1->CR1 |= I2C_CR1_STOP; + return false; + } + + error=false; + waiting=Thread::getCurrentThread(); + + #ifdef I2C_WITH_DMA + I2C1->CR2 |= I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN; + + DMA1_Stream0->CR=0; + DMA1_Stream0->PAR=reinterpret_cast(&I2C1->DR); + DMA1_Stream0->M0AR=reinterpret_cast(data); + DMA1_Stream0->NDTR=len; + DMA1_Stream0->FCR=DMA_SxFCR_FEIE + | DMA_SxFCR_DMDIS; + DMA1_Stream0->CR=DMA_SxCR_CHSEL_0 //Channel 1 + | DMA_SxCR_MINC //Increment memory pointer + | DMA_SxCR_TCIE //Interrupt on transfer complete + | DMA_SxCR_TEIE //Interrupt on transfer error + | DMA_SxCR_DMEIE //Interrupt on direct mode error + | DMA_SxCR_EN; //Start DMA + + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + + DMA1_Stream7->CR=0; + + I2C1->CR2 &= ~(I2C_CR2_DMAEN | I2C_CR2_LAST | I2C_CR2_ITERREN); + + #else + + /* Since i2c data reception is a bit tricky (see ST's reference manual for + * further details), the thread that calls recv is yelded and reception is + * handled using interrupts only if the number of bytes to be received is + * greater than one. + */ + + rxBuf = reinterpret_cast(data); + + if(len > 1) + { + I2C1->CR2 |= I2C_CR2_ITERREN | I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN; + + rxBufCnt = 0; + rxBufSize = len-2; + + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITBUFEN); + } + + I2C1->CR1 &= ~I2C_CR1_ACK; + I2C1->CR1 |= I2C_CR1_STOP; + + while(!(I2C1->SR1 & I2C_SR1_RXNE)) ; + rxBuf[len-1] = I2C1->DR; + + //set pointer to rx buffer to zero after having used it, see i2c event ISR + rxBuf = 0; + + I2C1->CR2 &= ~I2C_CR2_ITERREN; + #endif + + while(I2C1->SR2 & I2C_SR2_MSL) ; //Wait for stop bit sent + return !error; +} + +bool I2C1Driver::start(unsigned char address, bool immediateNak) +{ + /* Because the only way to send a restart is having the send function not + * sending a stop condition after the data transfer, here we have to manage + * a couple of things in SR1: + * - the BTF flag is set, cleared by a dummy read of DR + * - The Berr flag is set, this because the I2C harware detects the start + * condition sent without a stop before it as a misplaced start and + * rises an error + */ + + I2C1->CR1 |= I2C_CR1_START | I2C_CR1_ACK; + if(!waitStatus1()) return false; + if((I2C1->SR1 & I2C_SR1_SB)==0) return false; //Must read SR1 to clear flag + I2C1->DR=address; + if(immediateNak) I2C1->CR1 &= ~I2C_CR1_ACK; + if(!waitStatus1()) return false; + if(I2C1->SR1 & I2C_SR1_AF) return false; //Must read SR1 and SR2 + if((I2C1->SR2 & I2C_SR2_MSL)==0) return false; + return true; +} + +bool I2C1Driver::waitStatus1() +{ + error=false; + waiting=Thread::getCurrentThread(); + I2C1->CR2 |= I2C_CR2_ITEVTEN | I2C_CR2_ITERREN; + { + FastInterruptDisableLock dLock; + while(waiting) + { + waiting->IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + I2C1->CR2 &= ~(I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); + return !error; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/common/drivers/stm32f2_f4_i2c.h b/lib/miosix-kernel/miosix/arch/common/drivers/stm32f2_f4_i2c.h new file mode 100644 index 00000000..3884380c --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/common/drivers/stm32f2_f4_i2c.h @@ -0,0 +1,107 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico and Silvano Seva * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef STM32F2_I2C_H +#define STM32F2_I2C_H + +#include +#include "board_settings.h" + +namespace miosix { + +/** + * Driver for the I2C1 peripheral in STM32F2 and STM32F4 under Miosix + */ +class I2C1Driver +{ +public: + /** + * \return an instance of this class (singleton) + */ + static I2C1Driver& instance(); + + /** + * Initializes the peripheral. The only supported mode is 100KHz, master, + * 7bit address. Note that there is no need to manually call this member + * function as the constructor already inizializes the I2C peripheral. + * The only use of this member function is to reinitialize the peripheral + * if the microcontroller clock frequency or the APB prescaler is changed. + */ + void init(); + + /** + * Send data to a device connected to the I2C bus + * \param address device address (bit 0 is forced at 0) + * \param data pointer with data to send + * \param len length of data to send + * \param sendStop if set to false disables the sending of a stop condition + * after data transmission has finished + * \return true on success, false on failure + */ + bool send(unsigned char address, + const void *data, int len, bool sendStop = true); + + /** + * Receive data from a device connected to the I2C bus + * \param address device address (bit 0 is forced at 1) + * \param data pointer to a buffer where data will be received + * \param len length of data to receive + * \return true on success, false on failure + */ + bool recv(unsigned char address, void *data, int len); + +private: + I2C1Driver(const I2C1Driver&); + I2C1Driver& operator=(const I2C1Driver&); + + /** + * Constructor. Initializes the peripheral except the GPIOs, that must be + * set by the caller to the appropriate alternate function mode prior to + * creating an instance of this class. + * \param i2c pinter to the desired I2C peripheral, such as I2C1, I2C2, ... + */ + I2C1Driver() { init(); } + + /** + * Send a start condition + * \param address + * \param immediateNak + * \return + */ + bool start(unsigned char address, bool immediateNak=false); + + /** + * Wait until until an interrupt occurs during the send start bit and + * send address phases of the i2c communication. + * \return true if the operation was successful, false on error + */ + bool waitStatus1(); +}; + +} //namespace miosix + +#endif //STM32F2_I2C_H diff --git a/lib/miosix-kernel/miosix/arch/cortexM0/arch_registers_impl.h b/lib/miosix-kernel/miosix/arch/cortexM0/arch_registers_impl.h new file mode 100644 index 00000000..d290acc0 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM0/arch_registers_impl.h @@ -0,0 +1,12 @@ + +#ifndef ARCH_REGISTERS_IMPL_H +#define ARCH_REGISTERS_IMPL_H + +//Always include stm32f0xx.h before core_cm0.h, there's some nasty dependency +#include "CMSIS/Device/ST/STM32F0xx/Include/stm32f0xx.h" +#include "CMSIS/Include/core_cm0.h" +#include "CMSIS/Device/ST/STM32F0xx/Include/system_stm32f0xx.h" + +#define RCC_SYNC() //Workaround for a bug in stm32f42x + +#endif //ARCH_REGISTERS_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/cortexM0/arch_settings.h b/lib/miosix-kernel/miosix/arch/cortexM0/arch_settings.h new file mode 100644 index 00000000..3287b3b8 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM0/arch_settings.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ARCH_SETTINGS_H +#define ARCH_SETTINGS_H + +namespace miosix { + +/** + * \addtogroup Settings + * \{ + */ + +/// \internal Size of vector to store registers during ctx switch (9*4=36Bytes) +/// Only sp and r4-r11 are saved here, since r0-r3,r12,lr,pc,xPSR and +/// old sp are saved by hardware on the process stack on Cortex M3 CPUs. +const unsigned char CTXSAVE_SIZE=9; + +/// \internal some architectures save part of the context on their stack. +/// This constant is used to increase the stack size by the size of context +/// save frame. If zero, this architecture does not save anything on stack +/// during context save. Size is in bytes, not words. +/// MUST be divisible by 4. +const unsigned int CTXSAVE_ON_STACK=32; + +/// \internal stack alignment for this specific architecture +const unsigned int CTXSAVE_STACK_ALIGNMENT=8; + +/** + * \} + */ + +} //namespace miosix + +#endif /* ARCH_SETTINGS_H */ diff --git a/lib/miosix-kernel/miosix/arch/cortexM0/portability.cpp b/lib/miosix-kernel/miosix/arch/cortexM0/portability.cpp new file mode 100644 index 00000000..13a87815 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM0/portability.cpp @@ -0,0 +1,218 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#include "interfaces/portability.h" +#include "kernel/kernel.h" +#include "kernel/error.h" +#include "interfaces/bsp.h" +#include "kernel/scheduler/scheduler.h" +#include "kernel/scheduler/tick_interrupt.h" +#include + +/** + * \internal + * timer interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_preempt() + */ +void SysTick_Handler() __attribute__((naked)); +void SysTick_Handler() +{ + saveContext(); + //Call ISR_preempt(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private11ISR_preemptEv"); + restoreContext(); +} + +/** + * \internal + * software interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void SVC_Handler() __attribute__((naked)); +void SVC_Handler() +{ + saveContext(); + //Call ISR_yield(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private9ISR_yieldEv"); + restoreContext(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void XXX_IRQHandler() __attribute__((naked)); +void XXX_IRQHandler() +{ + saveContext(); + //Call ISR_auxTimer(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private12ISR_auxTimerEv"); + restoreContext(); +} +#endif //SCHED_TYPE_CONTROL_BASED + +namespace miosix_private { + +/** + * \internal + * Called by the timer interrupt, preempt to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_preempt() __attribute__((noinline)); +void ISR_preempt() +{ + IRQstackOverflowCheck(); + miosix::IRQtickInterrupt(); +} + +/** + * \internal + * Called by the software interrupt, yield to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_yield() __attribute__((noinline)); +void ISR_yield() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + */ +void ISR_auxTimer() __attribute__((noinline)); +void ISR_auxTimer() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread();//If the kernel is running, preempt + if(miosix::kernel_running!=0) miosix::tick_skew=true; + TIM2->SR=0; +} +#endif //SCHED_TYPE_CONTROL_BASED + +void IRQstackOverflowCheck() +{ + const unsigned int watermarkSize=miosix::WATERMARK_LEN/sizeof(unsigned int); + for(unsigned int i=0;iwatermark[i]!=miosix::WATERMARK_FILL) + miosix::errorHandler(miosix::STACK_OVERFLOW); + } + if(miosix::cur->ctxsave[0] < reinterpret_cast( + miosix::cur->watermark+watermarkSize)) + miosix::errorHandler(miosix::STACK_OVERFLOW); +} + +void IRQsystemReboot() +{ + NVIC_SystemReset(); +} + +void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp, + void *argv) +{ + unsigned int *stackPtr=sp; + stackPtr--; //Stack is full descending, so decrement first + *stackPtr=0x01000000; stackPtr--; //--> xPSR + *stackPtr=reinterpret_cast( + &miosix::Thread::threadLauncher); stackPtr--; //--> pc + *stackPtr=0xffffffff; stackPtr--; //--> lr + *stackPtr=0; stackPtr--; //--> r12 + *stackPtr=0; stackPtr--; //--> r3 + *stackPtr=0; stackPtr--; //--> r2 + *stackPtr=reinterpret_cast(argv); stackPtr--; //--> r1 + *stackPtr=reinterpret_cast(pc); //--> r0 + + ctxsave[0]=reinterpret_cast(stackPtr); //--> psp + //leaving the content of r4-r11 uninitialized +} + +void IRQportableStartKernel() +{ + NVIC_SetPriority(SVC_IRQn,3);//High priority for SVC (Max=0, min=15) + NVIC_SetPriority(SysTick_IRQn,3);//High priority for SysTick (Max=0, min=15) + SysTick->LOAD=SystemCoreClock/miosix::TICK_FREQ; + //Start SysTick, set to generate interrupts + SysTick->CTRL=SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk | + SysTick_CTRL_CLKSOURCE_Msk; + + #ifdef SCHED_TYPE_CONTROL_BASED + AuxiliaryTimer::IRQinit(); + #endif //SCHED_TYPE_CONTROL_BASED + + //create a temporary space to save current registers. This data is useless + //since there's no way to stop the sheduler, but we need to save it anyway. + unsigned int s_ctxsave[miosix::CTXSAVE_SIZE]; + ctxsave=s_ctxsave;//make global ctxsave point to it + //Note, we can't use enableInterrupts() now since the call is not mathced + //by a call to disableInterrupts() + __enable_irq(); + miosix::Thread::yield(); + //Never reaches here +} + +void sleepCpu() +{ + __WFI(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +#error "AUX_TIMER not yet implemented" +void AuxiliaryTimer::IRQinit() +{ + +} + +int AuxiliaryTimer::IRQgetValue() +{ + +} + +void AuxiliaryTimer::IRQsetValue(int x) +{ + +} +#endif //SCHED_TYPE_CONTROL_BASED + +} //namespace miosix_private diff --git a/lib/miosix-kernel/miosix/arch/cortexM0/portability_impl.h b/lib/miosix-kernel/miosix/arch/cortexM0/portability_impl.h new file mode 100644 index 00000000..c3e7e37d --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM0/portability_impl.h @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +//Miosix kernel + +#ifndef PORTABILITY_IMPL_H +#define PORTABILITY_IMPL_H + +#include "interfaces/arch_registers.h" +#include "config/miosix_settings.h" + +/** + * \addtogroup Drivers + * \{ + */ + +/* + * This pointer is used by the kernel, and should not be used by end users. + * this is a pointer to a location where to store the thread's registers during + * context switch. It requires C linkage to be used inside asm statement. + * Registers are saved in the following order: + * *ctxsave+32 --> r11 + * *ctxsave+28 --> r10 + * *ctxsave+24 --> r9 + * *ctxsave+20 --> r8 + * *ctxsave+16 --> r7 + * *ctxsave+12 --> r6 + * *ctxsave+8 --> r5 + * *ctxsave+4 --> r4 + * *ctxsave+0 --> psp + */ +extern "C" { +extern volatile unsigned int *ctxsave; +} + +/** + * \internal + * \def saveContext() + * Save context from an interrupt
+ * Must be the first line of an IRQ where a context switch can happen. + * The IRQ must be "naked" to prevent the compiler from generating context save. + * + * A note on the dmb instruction, without it a race condition was observed + * between pauseKernel() and IRQfindNextThread(). pauseKernel() uses an strex + * instruction to store a value in the global variable kernel_running which is + * tested by the context switch code in IRQfindNextThread(). Without the memory + * barrier IRQfindNextThread() would occasionally read the previous value and + * perform a context switch while the kernel was paused, leading to deadlock. + * The failure was only observed within the exception_test() in the testsuite + * running on the stm32f429zi_stm32f4discovery. + */ + +#define saveContext() \ +{ \ + asm volatile("push {lr} \n\t" /*save lr on MAIN stack*/ \ + "mrs r1, psp \n\t" /*get PROCESS stack pointer*/ \ + "ldr r0, =ctxsave \n\t" /*get current context*/ \ + "ldr r0, [r0] \n\t" \ + "stmia r0, {r1,r4-r7} \n\t" /*save PROCESS sp + r4-r7*/ \ + "mov r4, r8 \n\t" \ + "mov r5, r9 \n\t" \ + "mov r6, r10 \n\t" \ + "mov r7, r11 \n\t" \ + "stmia r0, {r4-r7} \n\t" \ + "dmb \n\t" \ + ); \ +} + +/** + * \def restoreContext() + * Restore context in an IRQ where saveContext() is used. Must be the last line + * of an IRQ where a context switch can happen. The IRQ must be "naked" to + * prevent the compiler from generating context restore. + */ + +#define restoreContext() \ +{ \ + asm volatile("ldr r0, =ctxsave \n\t" /*get current context*/ \ + "ldr r0, [r0] \n\t" \ + "ldmia r0, {r1,r4-r7} \n\t" /*pop r8-r11 saving in r4-r7*/ \ + "msr psp, r1 \n\t" /*restore PROCESS sp*/ \ + "ldmia r0!, {r0-r3} \n\t" \ + "mov r9, r0 \n\t" \ + "mov r10, r1 \n\t" \ + "mov r11, r2 \n\t" \ + "mov r11, r3 \n\t" \ + "pop {pc} \n\t" /*return*/ \ + ); \ +} + +/** + * \} + */ + +namespace miosix_private { + +/** + * \addtogroup Drivers + * \{ + */ + +inline void doYield() +{ + asm volatile("movs r3, #0\n\t" + "svc 0" + :::"r3"); +} + +inline void doDisableInterrupts() +{ + // Documentation says __disable_irq() disables all interrupts with + // configurable priority, so also SysTick and SVC. + // No need to disable faults with __disable_fault_irq() + __disable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline void doEnableInterrupts() +{ + __enable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline bool checkAreInterruptsEnabled() +{ + register int i; + asm volatile("mrs %0, primask \n\t":"=r"(i)); + if(i!=0) return false; + return true; +} + +/** + * \} + */ + +} //namespace miosix_private + +#endif //PORTABILITY_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/cortexM3/arch_registers_impl.h b/lib/miosix-kernel/miosix/arch/cortexM3/arch_registers_impl.h new file mode 100644 index 00000000..33653286 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM3/arch_registers_impl.h @@ -0,0 +1,12 @@ + +#ifndef ARCH_REGISTERS_IMPL_H +#define ARCH_REGISTERS_IMPL_H + +//Always include stm32f10x.h before core_cm3.h, there's some nasty dependency +#include "CMSIS/Device/ST/STM32F10x/Include/stm32f10x.h" +#include "CMSIS/Include/core_cm3.h" +#include "CMSIS/Device/ST/STM32F10x/Include/system_stm32f10x.h" + +#define RCC_SYNC() //Workaround for a bug in stm32f42x + +#endif //ARCH_REGISTERS_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/cortexM3/arch_settings.h b/lib/miosix-kernel/miosix/arch/cortexM3/arch_settings.h new file mode 100644 index 00000000..3287b3b8 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM3/arch_settings.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ARCH_SETTINGS_H +#define ARCH_SETTINGS_H + +namespace miosix { + +/** + * \addtogroup Settings + * \{ + */ + +/// \internal Size of vector to store registers during ctx switch (9*4=36Bytes) +/// Only sp and r4-r11 are saved here, since r0-r3,r12,lr,pc,xPSR and +/// old sp are saved by hardware on the process stack on Cortex M3 CPUs. +const unsigned char CTXSAVE_SIZE=9; + +/// \internal some architectures save part of the context on their stack. +/// This constant is used to increase the stack size by the size of context +/// save frame. If zero, this architecture does not save anything on stack +/// during context save. Size is in bytes, not words. +/// MUST be divisible by 4. +const unsigned int CTXSAVE_ON_STACK=32; + +/// \internal stack alignment for this specific architecture +const unsigned int CTXSAVE_STACK_ALIGNMENT=8; + +/** + * \} + */ + +} //namespace miosix + +#endif /* ARCH_SETTINGS_H */ diff --git a/lib/miosix-kernel/miosix/arch/cortexM3/portability.cpp b/lib/miosix-kernel/miosix/arch/cortexM3/portability.cpp new file mode 100644 index 00000000..0ff10b47 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM3/portability.cpp @@ -0,0 +1,273 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#include "interfaces/portability.h" +#include "kernel/kernel.h" +#include "kernel/error.h" +#include "interfaces/bsp.h" +#include "kernel/scheduler/scheduler.h" +#include "kernel/scheduler/tick_interrupt.h" +#include + +/** + * \internal + * timer interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_preempt() + */ +void SysTick_Handler() __attribute__((naked)); +void SysTick_Handler() +{ + saveContext(); + //Call ISR_preempt(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private11ISR_preemptEv"); + restoreContext(); +} + +/** + * \internal + * software interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void SVC_Handler() __attribute__((naked)); +void SVC_Handler() +{ + saveContext(); + //Call ISR_yield(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private9ISR_yieldEv"); + restoreContext(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void TIM2_IRQHandler() __attribute__((naked)); +void TIM2_IRQHandler() +{ + saveContext(); + //Call ISR_auxTimer(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private12ISR_auxTimerEv"); + restoreContext(); +} +#endif //SCHED_TYPE_CONTROL_BASED + +namespace miosix_private { + +/** + * \internal + * Called by the timer interrupt, preempt to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_preempt() __attribute__((noinline)); +void ISR_preempt() +{ + IRQstackOverflowCheck(); + miosix::IRQtickInterrupt(); +} + +/** + * \internal + * Called by the software interrupt, yield to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_yield() __attribute__((noinline)); +void ISR_yield() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + */ +void ISR_auxTimer() __attribute__((noinline)); +void ISR_auxTimer() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread();//If the kernel is running, preempt + if(miosix::kernel_running!=0) miosix::tick_skew=true; + TIM2->SR=0; +} +#endif //SCHED_TYPE_CONTROL_BASED + +void IRQstackOverflowCheck() +{ + const unsigned int watermarkSize=miosix::WATERMARK_LEN/sizeof(unsigned int); + for(unsigned int i=0;iwatermark[i]!=miosix::WATERMARK_FILL) + miosix::errorHandler(miosix::STACK_OVERFLOW); + } + if(miosix::cur->ctxsave[0] < reinterpret_cast( + miosix::cur->watermark+watermarkSize)) + miosix::errorHandler(miosix::STACK_OVERFLOW); +} + +void IRQsystemReboot() +{ + NVIC_SystemReset(); +} + +void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp, + void *argv) +{ + unsigned int *stackPtr=sp; + stackPtr--; //Stack is full descending, so decrement first + *stackPtr=0x01000000; stackPtr--; //--> xPSR + *stackPtr=reinterpret_cast( + &miosix::Thread::threadLauncher); stackPtr--; //--> pc + *stackPtr=0xffffffff; stackPtr--; //--> lr + *stackPtr=0; stackPtr--; //--> r12 + *stackPtr=0; stackPtr--; //--> r3 + *stackPtr=0; stackPtr--; //--> r2 + *stackPtr=reinterpret_cast(argv); stackPtr--; //--> r1 + *stackPtr=reinterpret_cast(pc); //--> r0 + + ctxsave[0]=reinterpret_cast(stackPtr); //--> psp + //leaving the content of r4-r11 uninitialized +} + +#ifdef WITH_PROCESSES + +void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp, + void *argv, unsigned int *gotBase) +{ + unsigned int *stackPtr=sp; + stackPtr--; //Stack is full descending, so decrement first + *stackPtr=0x01000000; stackPtr--; //--> xPSR + *stackPtr=reinterpret_cast(pc); stackPtr--; //--> pc + *stackPtr=0xffffffff; stackPtr--; //--> lr + *stackPtr=0; stackPtr--; //--> r12 + *stackPtr=0; stackPtr--; //--> r3 + *stackPtr=0; stackPtr--; //--> r2 + *stackPtr=0; stackPtr--; //--> r1 + *stackPtr=reinterpret_cast(argv); //--> r0 + + ctxsave[0]=reinterpret_cast(stackPtr); //--> psp + ctxsave[6]=reinterpret_cast(gotBase); //--> r9 + //leaving the content of r4-r8,r10-r11 uninitialized +} + +#endif //WITH_PROCESSES + +void IRQportableStartKernel() +{ + //Enable fault handlers + SCB->SHCSR |= SCB_SHCSR_USGFAULTENA | SCB_SHCSR_BUSFAULTENA + | SCB_SHCSR_MEMFAULTENA; + //Enable traps for division by zero. Trap for unaligned memory access + //was removed as gcc starting from 4.7.2 generates unaligned accesses by + //default (https://www.gnu.org/software/gcc/gcc-4.7/changes.html) + SCB->CCR |= SCB_CCR_DIV_0_TRP; + NVIC_SetPriorityGrouping(7);//This should disable interrupt nesting + NVIC_SetPriority(SVCall_IRQn,3);//High priority for SVC (Max=0, min=15) + NVIC_SetPriority(SysTick_IRQn,3);//High priority for SysTick (Max=0, min=15) + SysTick->LOAD=SystemCoreClock/miosix::TICK_FREQ; + //Start SysTick, set to generate interrupts + SysTick->CTRL=SysTick_CTRL_ENABLE | SysTick_CTRL_TICKINT | + SysTick_CTRL_CLKSOURCE; + + #ifdef SCHED_TYPE_CONTROL_BASED + AuxiliaryTimer::IRQinit(); + #endif //SCHED_TYPE_CONTROL_BASED + + //create a temporary space to save current registers. This data is useless + //since there's no way to stop the sheduler, but we need to save it anyway. + unsigned int s_ctxsave[miosix::CTXSAVE_SIZE]; + ctxsave=s_ctxsave;//make global ctxsave point to it + //Note, we can't use enableInterrupts() now since the call is not mathced + //by a call to disableInterrupts() + __enable_fault_irq(); + __enable_irq(); + miosix::Thread::yield(); + //Never reaches here +} + +void sleepCpu() +{ + __WFI(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +void AuxiliaryTimer::IRQinit() +{ + RCC->APB1ENR|=RCC_APB1ENR_TIM2EN; + RCC_SYNC(); + DBGMCU->CR|=DBGMCU_CR_DBG_TIM2_STOP; //Tim2 stops while debugging + TIM2->CR1=0; //Upcounter, not started, no special options + TIM2->CR2=0; //No special options + TIM2->SMCR=0; //No external trigger + TIM2->CNT=0; //Clear timer + TIM2->PSC=(SystemCoreClock/miosix::AUX_TIMER_CLOCK)-1; + TIM2->ARR=0xffff; //Count from zero to 0xffff + TIM2->DIER=TIM_DIER_CC1IE; //Enable interrupt on compare + TIM2->CCR1=0xffff; //This will be initialized later with setValue + NVIC_SetPriority(TIM2_IRQn,3);//High priority for TIM2 (Max=0, min=15) + NVIC_EnableIRQ(TIM2_IRQn); + TIM2->CR1=TIM_CR1_CEN; //Start timer + //This is very important: without this the prescaler shadow register may + //not be updated + TIM2->EGR=TIM_EGR_UG; +} + +int AuxiliaryTimer::IRQgetValue() +{ + return static_cast(TIM2->CNT); +} + +void AuxiliaryTimer::IRQsetValue(int x) +{ + TIM2->CR1=0; //Stop timer since changing CNT or CCR1 while running fails + TIM2->CNT=0; + TIM2->CCR1=static_cast(std::min(x,0xffff)); + TIM2->CR1=TIM_CR1_CEN; //Start timer again + //The above instructions cause a spurious if not called within the + //timer 2 IRQ (This happens if called from an SVC). + //Clearing the pending bit prevents this spurious interrupt + TIM2->SR=0; + NVIC_ClearPendingIRQ(TIM2_IRQn); +} +#endif //SCHED_TYPE_CONTROL_BASED + +} //namespace miosix_private diff --git a/lib/miosix-kernel/miosix/arch/cortexM3/portability_impl.h b/lib/miosix-kernel/miosix/arch/cortexM3/portability_impl.h new file mode 100644 index 00000000..2b6773e5 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM3/portability_impl.h @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +//Miosix kernel + +#ifndef PORTABILITY_IMPL_H +#define PORTABILITY_IMPL_H + +#include "interfaces/arch_registers.h" +#include "config/miosix_settings.h" + +/** + * \addtogroup Drivers + * \{ + */ + +/* + * This pointer is used by the kernel, and should not be used by end users. + * this is a pointer to a location where to store the thread's registers during + * context switch. It requires C linkage to be used inside asm statement. + * Registers are saved in the following order: + * *ctxsave+32 --> r11 + * *ctxsave+28 --> r10 + * *ctxsave+24 --> r9 + * *ctxsave+20 --> r8 + * *ctxsave+16 --> r7 + * *ctxsave+12 --> r6 + * *ctxsave+8 --> r5 + * *ctxsave+4 --> r4 + * *ctxsave+0 --> psp + */ +extern "C" { +extern volatile unsigned int *ctxsave; +} + +/** + * \internal + * \def saveContext() + * Save context from an interrupt
+ * Must be the first line of an IRQ where a context switch can happen. + * The IRQ must be "naked" to prevent the compiler from generating context save. + * + * A note on the dmb instruction, without it a race condition was observed + * between pauseKernel() and IRQfindNextThread(). pauseKernel() uses an strex + * instruction to store a value in the global variable kernel_running which is + * tested by the context switch code in IRQfindNextThread(). Without the memory + * barrier IRQfindNextThread() would occasionally read the previous value and + * perform a context switch while the kernel was paused, leading to deadlock. + * The failure was only observed within the exception_test() in the testsuite + * running on the stm32f429zi_stm32f4discovery. + */ +#define saveContext() \ +{ \ + asm volatile("stmdb sp!, {lr} \n\t" /*save lr on MAIN stack*/ \ + "mrs r1, psp \n\t" /*get PROCESS stack pointer*/ \ + "ldr r0, =ctxsave \n\t" /*get current context*/ \ + "ldr r0, [r0] \n\t" \ + "stmia r0, {r1,r4-r11} \n\t" /*save PROCESS sp + r4-r11*/ \ + "dmb \n\t" \ + ); \ +} + +/** + * \def restoreContext() + * Restore context in an IRQ where saveContext() is used. Must be the last line + * of an IRQ where a context switch can happen. The IRQ must be "naked" to + * prevent the compiler from generating context restore. + */ +#define restoreContext() \ +{ \ + asm volatile("ldr r0, =ctxsave \n\t" /*get current context*/ \ + "ldr r0, [r0] \n\t" \ + "ldmia r0, {r1,r4-r11} \n\t" /*restore r4-r11 + r1=psp*/ \ + "msr psp, r1 \n\t" /*restore PROCESS sp*/ \ + "ldmia sp!, {pc} \n\t" /*return*/ \ + ); \ +} + +/** + * \} + */ + +namespace miosix_private { + +/** + * \addtogroup Drivers + * \{ + */ + +inline void doYield() +{ + asm volatile("movs r3, #0\n\t" + "svc 0" + :::"r3"); +} + +inline void doDisableInterrupts() +{ + // Documentation says __disable_irq() disables all interrupts with + // configurable priority, so also SysTick and SVC. + // No need to disable faults with __disable_fault_irq() + __disable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline void doEnableInterrupts() +{ + __enable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline bool checkAreInterruptsEnabled() +{ + register int i; + asm volatile("mrs %0, primask \n\t":"=r"(i)); + if(i!=0) return false; + return true; +} + +/** + * \} + */ + +} //namespace miosix_private + +#endif //PORTABILITY_IMPL_H diff --git a/lib/miosix-kernel/miosix/arch/cortexM4/arch_settings.h b/lib/miosix-kernel/miosix/arch/cortexM4/arch_settings.h new file mode 100644 index 00000000..25387d2e --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM4/arch_settings.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2015-2020 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#pragma once + +namespace miosix { + +/** + * \addtogroup Settings + * \{ + */ + +/// \internal Size of vector to store registers during ctx switch (9*4=36Bytes) +/// Only sp and r4-r11 are saved here, since r0-r3,r12,lr,pc,xPSR and +/// old sp are saved by hardware on the process stack on Cortex CPUs. +const unsigned char CTXSAVE_SIZE=9; + +/// \internal some architectures save part of the context on their stack. +/// This constant is used to increase the stack size by the size of context +/// save frame. If zero, this architecture does not save anything on stack +/// during context save. Size is in bytes, not words. +/// MUST be divisible by 4. +const unsigned int CTXSAVE_ON_STACK=32; + +/// \internal stack alignment for this specific architecture +const unsigned int CTXSAVE_STACK_ALIGNMENT=8; + +/** + * \} + */ + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/arch/cortexM4/portability.cpp b/lib/miosix-kernel/miosix/arch/cortexM4/portability.cpp new file mode 100644 index 00000000..49aebdc1 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM4/portability.cpp @@ -0,0 +1,233 @@ +/*************************************************************************** + * Copyright (C) 2010-2020 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#include "interfaces/portability.h" +#include "kernel/kernel.h" +#include "kernel/error.h" +#include "interfaces/bsp.h" +#include "kernel/scheduler/scheduler.h" +#include "kernel/scheduler/tick_interrupt.h" +#include + +extern void (* const __Vectors[])(); + +/** + * \internal + * timer interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_preempt() + */ +void SysTick_Handler() __attribute__((naked)); +void SysTick_Handler() +{ + saveContext(); + //Call ISR_preempt(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private11ISR_preemptEv"); + restoreContext(); +} + +/** + * \internal + * software interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void SVC_Handler() __attribute__((naked)); +void SVC_Handler() +{ + saveContext(); + //Call ISR_yield(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private9ISR_yieldEv"); + restoreContext(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void XXX_IRQHandler() __attribute__((naked)); +void XXX_IRQHandler() +{ + saveContext(); + //Call ISR_auxTimer(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private12ISR_auxTimerEv"); + restoreContext(); +} +#endif //SCHED_TYPE_CONTROL_BASED + +namespace miosix_private { + +/** + * \internal + * Called by the timer interrupt, preempt to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_preempt() __attribute__((noinline)); +void ISR_preempt() +{ + IRQstackOverflowCheck(); + miosix::IRQtickInterrupt(); +} + +/** + * \internal + * Called by the software interrupt, yield to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_yield() __attribute__((noinline)); +void ISR_yield() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + */ +void ISR_auxTimer() __attribute__((noinline)); +void ISR_auxTimer() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread();//If the kernel is running, preempt + if(miosix::kernel_running!=0) miosix::tick_skew=true; +} +#endif //SCHED_TYPE_CONTROL_BASED + +void IRQstackOverflowCheck() +{ + const unsigned int watermarkSize=miosix::WATERMARK_LEN/sizeof(unsigned int); + for(unsigned int i=0;iwatermark[i]!=miosix::WATERMARK_FILL) + miosix::errorHandler(miosix::STACK_OVERFLOW); + } + if(miosix::cur->ctxsave[0] < reinterpret_cast( + miosix::cur->watermark+watermarkSize)) + miosix::errorHandler(miosix::STACK_OVERFLOW); +} + +void IRQsystemReboot() +{ + NVIC_SystemReset(); +} + +void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp, + void *argv) +{ + unsigned int *stackPtr=sp; + stackPtr--; //Stack is full descending, so decrement first + *stackPtr=0x01000000; stackPtr--; //--> xPSR + *stackPtr=reinterpret_cast( + &miosix::Thread::threadLauncher); stackPtr--; //--> pc + *stackPtr=0xffffffff; stackPtr--; //--> lr + *stackPtr=0; stackPtr--; //--> r12 + *stackPtr=0; stackPtr--; //--> r3 + *stackPtr=0; stackPtr--; //--> r2 + *stackPtr=reinterpret_cast(argv); stackPtr--; //--> r1 + *stackPtr=reinterpret_cast(pc); //--> r0 + + ctxsave[0]=reinterpret_cast(stackPtr); //--> psp + //leaving the content of r4-r11 uninitialized +} + +void IRQportableStartKernel() +{ + //NOTE: the SAM-BA bootloader does not relocate the vector table offset, + //so any interrupt would call the SAM-BA IRQ handler, not the application + //ones. + SCB->VTOR = reinterpret_cast(&__Vectors); + + //Enable fault handlers + SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk + | SCB_SHCSR_MEMFAULTENA_Msk; + //Enable traps for division by zero. Trap for unaligned memory access + //was removed as gcc starting from 4.7.2 generates unaligned accesses by + //default (https://www.gnu.org/software/gcc/gcc-4.7/changes.html) + SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk; + NVIC_SetPriorityGrouping(7);//This should disable interrupt nesting + NVIC_SetPriority(SVCall_IRQn,3);//High priority for SVC (Max=0, min=15) + NVIC_SetPriority(SysTick_IRQn,3);//High priority for SysTick (Max=0, min=15) + SysTick->LOAD=SystemCoreClock/miosix::TICK_FREQ-1; + //Start SysTick, set to generate interrupts + SysTick->CTRL=SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk | + SysTick_CTRL_CLKSOURCE_Msk; + + #ifdef SCHED_TYPE_CONTROL_BASED + AuxiliaryTimer::IRQinit(); + #endif //SCHED_TYPE_CONTROL_BASED + + //create a temporary space to save current registers. This data is useless + //since there's no way to stop the sheduler, but we need to save it anyway. + unsigned int s_ctxsave[miosix::CTXSAVE_SIZE]; + ctxsave=s_ctxsave;//make global ctxsave point to it + //Note, we can't use enableInterrupts() now since the call is not mathced + //by a call to disableInterrupts() + __enable_fault_irq(); + __enable_irq(); + miosix::Thread::yield(); + //Never reaches here +} + +void sleepCpu() +{ + __WFI(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +#error "AUX_TIMER not yet implemented" +void AuxiliaryTimer::IRQinit() +{ + +} + +int AuxiliaryTimer::IRQgetValue() +{ + +} + +void AuxiliaryTimer::IRQsetValue(int x) +{ + +} +#endif //SCHED_TYPE_CONTROL_BASED + +} //namespace miosix_private diff --git a/lib/miosix-kernel/miosix/arch/cortexM4/portability_impl.h b/lib/miosix-kernel/miosix/arch/cortexM4/portability_impl.h new file mode 100644 index 00000000..8e874a21 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM4/portability_impl.h @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 2010-2020 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#pragma once + +#include "interfaces/arch_registers.h" +#include "interfaces/portability.h" +#include "config/miosix_settings.h" + +/** + * \addtogroup Drivers + * \{ + */ + +/* + * This pointer is used by the kernel, and should not be used by end users. + * this is a pointer to a location where to store the thread's registers during + * context switch. It requires C linkage to be used inside asm statement. + * Registers are saved in the following order: + * *ctxsave+32 --> r11 + * *ctxsave+28 --> r10 + * *ctxsave+24 --> r9 + * *ctxsave+20 --> r8 + * *ctxsave+16 --> r7 + * *ctxsave+12 --> r6 + * *ctxsave+8 --> r5 + * *ctxsave+4 --> r4 + * *ctxsave+0 --> psp + */ +extern "C" { +extern volatile unsigned int *ctxsave; +} + +/** + * \internal + * \def saveContext() + * Save context from an interrupt
+ * Must be the first line of an IRQ where a context switch can happen. + * The IRQ must be "naked" to prevent the compiler from generating context save. + * + * A note on the dmb instruction, without it a race condition was observed + * between pauseKernel() and IRQfindNextThread(). pauseKernel() uses an strex + * instruction to store a value in the global variable kernel_running which is + * tested by the context switch code in IRQfindNextThread(). Without the memory + * barrier IRQfindNextThread() would occasionally read the previous value and + * perform a context switch while the kernel was paused, leading to deadlock. + * The failure was only observed within the exception_test() in the testsuite + * running on the stm32f429zi_stm32f4discovery. + */ +#define saveContext() \ +{ \ + asm volatile("stmdb sp!, {lr} \n\t" /*save lr on MAIN stack*/ \ + "mrs r1, psp \n\t" /*get PROCESS stack pointer*/ \ + "ldr r0, =ctxsave \n\t" /*get current context*/ \ + "ldr r0, [r0] \n\t" \ + "stmia r0, {r1,r4-r11} \n\t" /*save PROCESS sp + r4-r11*/ \ + "dmb \n\t" \ + ); \ +} + +/** + * \def restoreContext() + * Restore context in an IRQ where saveContext() is used. Must be the last line + * of an IRQ where a context switch can happen. The IRQ must be "naked" to + * prevent the compiler from generating context restore. + */ +#define restoreContext() \ +{ \ + asm volatile("ldr r0, =ctxsave \n\t" /*get current context*/ \ + "ldr r0, [r0] \n\t" \ + "ldmia r0, {r1,r4-r11} \n\t" /*restore r4-r11 + r1=psp*/ \ + "msr psp, r1 \n\t" /*restore PROCESS sp*/ \ + "ldmia sp!, {pc} \n\t" /*return*/ \ + ); \ +} + +/** + * \} + */ + +namespace miosix_private { + +/** + * \addtogroup Drivers + * \{ + */ + +inline void doYield() +{ + asm volatile("movs r3, #0\n\t" + "svc 0" + :::"r3"); +} + + +inline void doDisableInterrupts() +{ + // Documentation says __disable_irq() disables all interrupts with + // configurable priority, so also SysTick and SVC. + // No need to disable faults with __disable_fault_irq() + __disable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline void doEnableInterrupts() +{ + __enable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline bool checkAreInterruptsEnabled() +{ + register int i; + asm volatile("mrs %0, primask \n\t":"=r"(i)); + if(i!=0) return false; + return true; +} + +/** + * \} + */ + +} //namespace miosix_private diff --git a/lib/miosix-kernel/miosix/arch/cortexM4F/arch_settings.h b/lib/miosix-kernel/miosix/arch/cortexM4F/arch_settings.h new file mode 100644 index 00000000..ef8238cb --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM4F/arch_settings.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ARCH_SETTINGS_H +#define ARCH_SETTINGS_H + +namespace miosix { + +/** + * \addtogroup Settings + * \{ + */ + +/// \internal size of vector to store registers during ctx switch +/// ((10+16)*4=104Bytes). Only sp, r4-r11, EXC_RETURN and s16-s31 are saved +/// here, since r0-r3,r12,lr,pc,xPSR, old sp and s0-s15,fpscr are saved by +/// hardware on the process stack on Cortex M4F CPUs. EXC_RETURN, or the lr, +/// value to use to return from the exception is necessary to know if the +/// thread has used fp regs, as an extension specific to Cortex-M4F CPUs. +const unsigned char CTXSAVE_SIZE=10+16; + +/// \internal some architectures save part of the context on their stack. +/// ((8+17)*4=100Bytes). This constant is used to increase the stack size by +/// the size of context save frame. If zero, this architecture does not save +/// anything on stack during context save. Size is in bytes, not words. +/// 8 registers=r0-r3,r12,lr,pc,xPSR +/// 17 registers=s0-s15,fpscr +/// MUST be divisible by 4. +const unsigned int CTXSAVE_ON_STACK=(8+17)*4; + +/// \internal stack alignment for this specific architecture +const unsigned int CTXSAVE_STACK_ALIGNMENT=8; + +/** + * \} + */ + +} //namespace miosix + +#endif /* ARCH_SETTINGS_H */ diff --git a/lib/miosix-kernel/miosix/arch/cortexM4F/portability.cpp b/lib/miosix-kernel/miosix/arch/cortexM4F/portability.cpp new file mode 100644 index 00000000..a8428e53 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM4F/portability.cpp @@ -0,0 +1,213 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#include "interfaces/portability.h" +#include "kernel/kernel.h" +#include "kernel/error.h" +#include "interfaces/bsp.h" +#include "kernel/scheduler/scheduler.h" +#include "kernel/scheduler/tick_interrupt.h" +#include "core/interrupts.h" +#include "kernel/process.h" +#include +#include +#include +#include + +/** + * \internal + * timer interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_preempt() + */ +void SysTick_Handler() __attribute__((naked)); +void SysTick_Handler() +{ + saveContext(); + //Call ISR_preempt(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private11ISR_preemptEv"); + restoreContext(); +} + +/** + * \internal + * software interrupt routine. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void SVC_Handler() __attribute__((naked)); +void SVC_Handler() +{ + saveContext(); + //Call ISR_yield(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private9ISR_yieldEv"); + restoreContext(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + * Since inside naked functions only assembler code is allowed, this function + * only calls the ctxsave/ctxrestore macros (which are in assembler), and calls + * the implementation code in ISR_yield() + */ +void TIM3_IRQHandler() __attribute__((naked)); +void TIM3_IRQHandler() +{ + saveContext(); + //Call ISR_auxTimer(). Name is a C++ mangled name. + asm volatile("bl _ZN14miosix_private12ISR_auxTimerEv"); + restoreContext(); +} +#endif //SCHED_TYPE_CONTROL_BASED + +namespace miosix_private { + +/** + * \internal + * Called by the timer interrupt, preempt to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_preempt() __attribute__((noinline)); +void ISR_preempt() +{ + IRQstackOverflowCheck(); + miosix::IRQtickInterrupt(); +} + +/** + * \internal + * Called by the software interrupt, yield to next thread + * Declared noinline to avoid the compiler trying to inline it into the caller, + * which would violate the requirement on naked functions. Function is not + * static because otherwise the compiler optimizes it out... + */ +void ISR_yield() __attribute__((noinline)); +void ISR_yield() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread(); +} + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * \internal + * Auxiliary timer interupt routine. + * Used for variable lenght bursts in control based scheduler. + */ +void ISR_auxTimer() __attribute__((noinline)); +void ISR_auxTimer() +{ + IRQstackOverflowCheck(); + miosix::Scheduler::IRQfindNextThread();//If the kernel is running, preempt + if(miosix::kernel_running!=0) miosix::tick_skew=true; + TIM3->SR=0; +} +#endif //SCHED_TYPE_CONTROL_BASED + +void IRQstackOverflowCheck() +{ + const unsigned int watermarkSize=miosix::WATERMARK_LEN/sizeof(unsigned int); + for(unsigned int i=0;iwatermark[i]!=miosix::WATERMARK_FILL) + miosix::errorHandler(miosix::STACK_OVERFLOW); + } + if(miosix::cur->ctxsave[0] < reinterpret_cast( + miosix::cur->watermark+watermarkSize)) + miosix::errorHandler(miosix::STACK_OVERFLOW); +} + +void IRQsystemReboot() +{ + NVIC_SystemReset(); +} + +void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp, + void *argv) +{ + unsigned int *stackPtr=sp; + stackPtr--; //Stack is full descending, so decrement first + *stackPtr=0x01000000; stackPtr--; //--> xPSR + *stackPtr=reinterpret_cast( + &miosix::Thread::threadLauncher); stackPtr--; //--> pc + *stackPtr=0xffffffff; stackPtr--; //--> lr + *stackPtr=0; stackPtr--; //--> r12 + *stackPtr=0; stackPtr--; //--> r3 + *stackPtr=0; stackPtr--; //--> r2 + *stackPtr=reinterpret_cast(argv); stackPtr--; //--> r1 + *stackPtr=reinterpret_cast(pc); //--> r0 + + ctxsave[0]=reinterpret_cast(stackPtr); //--> psp + //leaving the content of r4-r11 uninitialized + ctxsave[9]=0xfffffffd; //EXC_RETURN=thread mode, use psp, no floating ops + //leaving the content of s16-s31 uninitialized +} + +void IRQportableStartKernel() +{ + //Enable fault handlers + SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_BUSFAULTENA_Msk + | SCB_SHCSR_MEMFAULTENA_Msk; + //Enable traps for division by zero. Trap for unaligned memory access + //was removed as gcc starting from 4.7.2 generates unaligned accesses by + //default (https://www.gnu.org/software/gcc/gcc-4.7/changes.html) + SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk; + NVIC_SetPriorityGrouping(7);//This should disable interrupt nesting + NVIC_SetPriority(SVCall_IRQn,3);//High priority for SVC (Max=0, min=15) + NVIC_SetPriority(SysTick_IRQn,3);//High priority for SysTick (Max=0, min=15) + NVIC_SetPriority(MemoryManagement_IRQn,2);//Higher priority for MemoryManagement (Max=0, min=15) + SysTick->LOAD=SystemCoreClock/miosix::TICK_FREQ; + //Start SysTick, set to generate interrupts + SysTick->CTRL=SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk | + SysTick_CTRL_CLKSOURCE_Msk; + + //create a temporary space to save current registers. This data is useless + //since there's no way to stop the sheduler, but we need to save it anyway. + unsigned int s_ctxsave[miosix::CTXSAVE_SIZE]; + ctxsave=s_ctxsave;//make global ctxsave point to it + //Note, we can't use enableInterrupts() now since the call is not mathced + //by a call to disableInterrupts() + __enable_fault_irq(); + __enable_irq(); + miosix::Thread::yield(); + //Never reaches here +} + +void sleepCpu() +{ + __WFI(); +} + +} //namespace miosix_private diff --git a/lib/miosix-kernel/miosix/arch/cortexM4F/portability_impl.h b/lib/miosix-kernel/miosix/arch/cortexM4F/portability_impl.h new file mode 100644 index 00000000..68fe4c66 --- /dev/null +++ b/lib/miosix-kernel/miosix/arch/cortexM4F/portability_impl.h @@ -0,0 +1,163 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ +//Miosix kernel + +#ifndef PORTABILITY_IMPL_H +#define PORTABILITY_IMPL_H + +#include "interfaces/arch_registers.h" +#include "interfaces/portability.h" +#include "config/miosix_settings.h" + +/** + * \addtogroup Drivers + * \{ + */ + +/* + * This pointer is used by the kernel, and should not be used by end users. + * this is a pointer to a location where to store the thread's registers during + * context switch. It requires C linkage to be used inside asm statement. + * Registers are saved in the following order: + * *ctxsave+100 --> s31 + * ... + * *ctxsave+40 --> s16 + * *ctxsave+36 --> lr (contains EXC_RETURN whose bit #4 tells if fpu is used) + * *ctxsave+32 --> r11 + * *ctxsave+28 --> r10 + * *ctxsave+24 --> r9 + * *ctxsave+20 --> r8 + * *ctxsave+16 --> r7 + * *ctxsave+12 --> r6 + * *ctxsave+8 --> r5 + * *ctxsave+4 --> r4 + * *ctxsave+0 --> psp + */ +extern "C" { +extern volatile unsigned int *ctxsave; +} + +/** + * \internal + * \def saveContext() + * Save context from an interrupt
+ * Must be the first line of an IRQ where a context switch can happen. + * The IRQ must be "naked" to prevent the compiler from generating context save. + * + * A note on the dmb instruction, without it a race condition was observed + * between pauseKernel() and IRQfindNextThread(). pauseKernel() uses an strex + * instruction to store a value in the global variable kernel_running which is + * tested by the context switch code in IRQfindNextThread(). Without the memory + * barrier IRQfindNextThread() would occasionally read the previous value and + * perform a context switch while the kernel was paused, leading to deadlock. + * The failure was only observed within the exception_test() in the testsuite + * running on the stm32f429zi_stm32f4discovery. + */ +#define saveContext() \ +{ \ + asm volatile(" mrs r1, psp \n"/*get PROCESS stack ptr */ \ + " ldr r0, =ctxsave \n"/*get current context */ \ + " ldr r0, [r0] \n" \ + " stmia r0!, {r1,r4-r11,lr} \n"/*save r1(psp),r4-r11,lr */ \ + " lsls r2, lr, #27 \n"/*check if bit #4 is set */ \ + " bmi 0f \n" \ + " vstmia.32 r0, {s16-s31} \n"/*save s16-s31 if we need*/ \ + "0: dmb \n" \ + ); \ +} + +/** + * \def restoreContext() + * Restore context in an IRQ where saveContext() is used. Must be the last line + * of an IRQ where a context switch can happen. The IRQ must be "naked" to + * prevent the compiler from generating context restore. + */ +#define restoreContext() \ +{ \ + asm volatile(" ldr r0, =ctxsave \n"/*get current context */ \ + " ldr r0, [r0] \n" \ + " ldmia r0!, {r1,r4-r11,lr} \n"/*load r1(psp),r4-r11,lr */ \ + " lsls r2, lr, #27 \n"/*check if bit #4 is set */ \ + " bmi 0f \n" \ + " vldmia.32 r0, {s16-s31} \n"/*restore s16-s31 if need*/ \ + "0: msr psp, r1 \n"/*restore PROCESS sp*/ \ + " bx lr \n"/*return*/ \ + ); \ +} + +/** + * \} + */ + +namespace miosix_private { + +/** + * \addtogroup Drivers + * \{ + */ + +inline void doYield() +{ + asm volatile("movs r3, #0\n\t" + "svc 0" + :::"r3"); +} + +inline void doDisableInterrupts() +{ + // Documentation says __disable_irq() disables all interrupts with + // configurable priority, so also SysTick and SVC. + // No need to disable faults with __disable_fault_irq() + __disable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline void doEnableInterrupts() +{ + __enable_irq(); + //The new fastDisableInterrupts/fastEnableInterrupts are inline, so there's + //the need for a memory barrier to avoid aggressive reordering + asm volatile("":::"memory"); +} + +inline bool checkAreInterruptsEnabled() +{ + register int i; + asm volatile("mrs %0, primask \n\t":"=r"(i)); + if(i!=0) return false; + return true; +} + +/** + * \} + */ + +} //namespace miosix_private + +#endif //PORTABILITY_IMPL_H diff --git a/lib/miosix-kernel/miosix/config/board_settings.h b/lib/miosix-kernel/miosix/config/board_settings.h new file mode 100644 index 00000000..5e279bd6 --- /dev/null +++ b/lib/miosix-kernel/miosix/config/board_settings.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef BOARD_SETTINGS_H +#define BOARD_SETTINGS_H + +#include "util/version.h" + +/** + * \internal + * Versioning for board_settings.h for out of git tree projects + */ +#define BOARD_SETTINGS_VERSION 100 + +namespace miosix { + +/** + * \addtogroup Settings + * \{ + */ + +/// Size of stack for main(). +/// The C standard library is stack-heavy (iprintf requires 1KB) but the +/// STM32F407VG only has 192KB of RAM so there is room for a big 4K stack. +const unsigned int MAIN_STACK_SIZE=4*1024; + +/// Frequency of tick (in Hz). The frequency of the STM32F100RB timer in the +/// stm32vldiscovery board can be divided by 1000. This allows to use a 1KHz +/// tick and the minimun Thread::sleep value is 1ms +/// For the priority scheduler this is also the context switch frequency +const unsigned int TICK_FREQ=1000; + +///\internal Aux timer run @ 100KHz +///Note that since the timer is only 16 bits this imposes a limit on the +///burst measurement of 655ms. If due to a pause_kernel() or +///disable_interrupts() section a thread runs for more than that time, a wrong +///burst value will be measured +const unsigned int AUX_TIMER_CLOCK=100000; +const unsigned int AUX_TIMER_MAX=0xffff; ///<\internal Aux timer is 16 bits + +/** + * \} + */ + +} //namespace miosix + +#endif /* BOARD_SETTINGS_H */ diff --git a/lib/miosix-kernel/miosix/config/miosix_settings.h b/lib/miosix-kernel/miosix/config/miosix_settings.h new file mode 100644 index 00000000..76ee6ee1 --- /dev/null +++ b/lib/miosix-kernel/miosix/config/miosix_settings.h @@ -0,0 +1,235 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 * + * by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef MIOSIX_SETTINGS_H +#define MIOSIX_SETTINGS_H + +// Before you can compile the kernel you have to configure it by editing this +// file. After that, comment out this line to disable the reminder error. +// The PARSING_FROM_IDE is because Netbeans gets confused by this, it is never +// defined when compiling the code. +#ifndef PARSING_FROM_IDE +// #error This error is a reminder that you have not edited miosix_settings.h yet. +#endif //PARSING_FROM_IDE + +/** + * \file miosix_settings.h + * NOTE: this file contains ONLY configuration options that are not dependent + * on architecture specific details. The other options are in the following + * files which are included here: + * miosix/arch/architecture name/common/arch_settings.h + * miosix/arch/architecture name/board name/board_settings.h + */ +#include "arch_settings.h" +#include "board_settings.h" +#include "util/version.h" + +/** + * \internal + * Versioning for miosix_settings.h for out of git tree projects + */ +#define MIOSIX_SETTINGS_VERSION 100 + +namespace miosix { + +/** + * \addtogroup Settings + * \{ + */ + +// +// Scheduler options +// + +/// \def SCHED_TYPE_PRIORITY +/// If uncommented selects the priority scheduler +/// \def SCHED_TYPE_CONTROL_BASED +/// If uncommented selects the control based scheduler +/// \def SCHED_TYPE_EDF +///If uncommented selects the EDF scheduler +//Uncomment only *one* of those + +#define SCHED_TYPE_PRIORITY +//#define SCHED_TYPE_CONTROL_BASED +//#define SCHED_TYPE_EDF + +// +// Filesystem options +// + +/// \def WITH_FILESYSTEM +/// Allows to enable/disable filesystem support to save code size +/// By default it is defined (filesystem support is enabled) +// #define WITH_FILESYSTEM + +/// \def WITH_DEVFS +/// Allows to enable/disable DevFs support to save code size +/// By default it is defined (DevFs is enabled) +// #define WITH_DEVFS + +/// \def SYNC_AFTER_WRITE +/// Increases filesystem write robustness. After each write operation the +/// filesystem is synced so that a power failure happens data is not lost +/// (unless power failure happens exactly between the write and the sync) +/// Unfortunately write latency and throughput becomes twice as worse +/// By default it is defined (slow but safe) +// #define SYNC_AFTER_WRITE + +/// Maximum number of open files. Trying to open more will fail. +/// Cannot be lower than 3, as the first three are stdin, stdout, stderr +const unsigned char MAX_OPEN_FILES=8; + +/// \def WITH_PROCESSES +/// If uncommented enables support for processes as well as threads. +/// This enables the dynamic loader to load elf programs, the extended system +/// call service and, if the hardware supports it, the MPU to provide memory +/// isolation of processes +//#define WITH_PROCESSES + +#if defined(WITH_PROCESSES) && defined(__NO_EXCEPTIONS) +#error Processes require C++ exception support +#endif //defined(WITH_PROCESSES) && defined(__NO_EXCEPTIONS) + +#if defined(WITH_PROCESSES) && !defined(WITH_FILESYSTEM) +#error Processes require filesystem support +#endif //defined(WITH_PROCESSES) && !defined(WITH_FILESYSTEM) + +#if defined(WITH_PROCESSES) && !defined(WITH_DEVFS) +#error Processes require devfs support +#endif //defined(WITH_PROCESSES) && !defined(WITH_DEVFS) + +// +// C/C++ standard library I/O (stdin, stdout and stderr related) +// + +/// \def WITH_BOOTLOG +/// Uncomment to print bootlogs on stdout. +/// By default it is defined (bootlogs are printed) +// #define WITH_BOOTLOG + +/// \def WITH_ERRLOG +/// Uncomment for debug information on stdout. +/// By default it is defined (error information is printed) +// #define WITH_ERRLOG + + + +// +// Kernel related options (stack sizes, priorities) +// + +/** + * \def JTAG_DISABLE_SLEEP + * JTAG debuggers lose communication with the device if it enters sleep + * mode, so to use debugging it is necessary to disable sleep in the idle thread. + * By default it is not defined (idle thread calls sleep). + */ +//#define JTAG_DISABLE_SLEEP + +/// Minimum stack size (MUST be divisible by 4) +const unsigned int STACK_MIN=256; + +/// \internal Size of idle thread stack. +/// Should be >=STACK_MIN (MUST be divisible by 4) +const unsigned int STACK_IDLE=256; + +/// Default stack size for pthread_create. +/// The chosen value is enough to call C standard library functions +/// such as printf/fopen which are stack-heavy +const unsigned int STACK_DEFAULT_FOR_PTHREAD=2048; + +/// Maximum size of the RAM image of a process. If a program requires more +/// the kernel will not run it (MUST be divisible by 4) +const unsigned int MAX_PROCESS_IMAGE_SIZE=64*1024; + +/// Minimum size of the stack for a process. If a program specifies a lower +/// size the kernel will not run it (MUST be divisible by 4) +const unsigned int MIN_PROCESS_STACK_SIZE=STACK_MIN; + +/// Every userspace thread has two stacks, one for when it is running in +/// userspace and one for when it is running in kernelspace (that is, while it +/// is executing system calls). This is the size of the stack for when the +/// thread is running in kernelspace (MUST be divisible by 4) +const unsigned int SYSTEM_MODE_PROCESS_STACK_SIZE=2*1024; + +/// Number of priorities (MUST be >1) +/// PRIORITY_MAX-1 is the highest priority, 0 is the lowest. -1 is reserved as +/// the priority of the idle thread. +/// The meaning of a thread's priority depends on the chosen scheduler. +#ifdef SCHED_TYPE_PRIORITY +//Can be modified, but a high value makes context switches more expensive +const short int PRIORITY_MAX=4; +#elif defined(SCHED_TYPE_CONTROL_BASED) +//Don't touch, the limit is due to the fixed point implementation +//It's not needed for if floating point is selected, but kept for consistency +const short int PRIORITY_MAX=64; +#else //SCHED_TYPE_EDF +//Doesn't exist for this kind of scheduler +#endif + +/// Priority of main() +/// The meaning of a thread's priority depends on the chosen scheduler. +const unsigned char MAIN_PRIORITY=1; + + + +// +// Other low level kernel options. There is usually no need to modify these. +// + +/// \internal Length of wartermark (in bytes) to check stack overflow. +/// MUST be divisible by 4 and can also be zero. +/// A high value increases context switch time. +const unsigned int WATERMARK_LEN=16; + +/// \internal Used to fill watermark +const unsigned int WATERMARK_FILL=0xaaaaaaaa; + +/// \internal Used to fill stack (for checking stack usage) +const unsigned int STACK_FILL=0xbbbbbbbb; + +// Compiler version checks +#if _MIOSIX_GCC_PATCH_MAJOR > 3 +#warning "You are using a too new compiler, which may not be supported" +#elif _MIOSIX_GCC_PATCH_MAJOR == 2 +#error "The compiler you are using has known incomplete patches and is not supported. Get the latest one from https://miosix.org/wiki/index.php?title=Miosix_Toolchain" +#elif _MIOSIX_GCC_PATCH_VERSION == 1 +#warning "You are using an unsupported compiler. Get the latest one from https://miosix.org/wiki/index.php?title=Miosix_Toolchain" +#endif +#if !defined(_MIOSIX_GCC_PATCH_MAJOR) && \ + (!defined(_MIOSIX_GCC_PATCH_VERSION) || _MIOSIX_GCC_PATCH_VERSION < 1) +#error "You are using an unsupported compiler. Get the latest one from https://miosix.org/wiki/index.php?title=Miosix_Toolchain" +#endif + +/** + * \} + */ + +} //namespace miosix + +#endif //MIOSIX_SETTINGS_H diff --git a/lib/miosix-kernel/miosix/e20/callback.h b/lib/miosix-kernel/miosix/e20/callback.h new file mode 100644 index 00000000..5993d54e --- /dev/null +++ b/lib/miosix-kernel/miosix/e20/callback.h @@ -0,0 +1,243 @@ +/*************************************************************************** + * Copyright (C) 2012, 2013, 2014, 2105, 2106 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef CALLBACK_H +#define CALLBACK_H + +#include + +namespace miosix { + +/** + * This class is to extract from Callback code that + * does not depend on the template parameter N. + */ +class CallbackBase +{ +protected: + /** + * Possible operations performed by TypeDependentOperation::operation() + */ + enum Op + { + CALL, + ASSIGN, + DESTROY + }; + /** + * This class is part of the any idiom used by Callback. + */ + template + class TypeDependentOperation + { + public: + /** + * Perform the type-dependent operations + * \param a storage for the any object, stores the function object + * \param b storage for the source object for the copy constructor + * \param op operation + */ + static void operation(int32_t *a, const int32_t *b, Op op) + { + T *o1=reinterpret_cast(a); + const T *o2=reinterpret_cast(b); + switch(op) + { + case CALL: + (*o1)(); + break; + case ASSIGN: + //This used to be simply *o1=*o2 when we were using + //tr1/functional, but in C++11 the type returned by bind + //due to having a move constructor doesn't like being + //assigned, only copy construction works so we have to + //use placement new + new (o1) T(*o2); + break; + case DESTROY: + o1->~T(); + break; + } + } + }; +}; + +/** + * A Callback works just like an std::function, but has some additional + * limitations. First, it can only accept function objects that take void + * as a parameter and return void, and second if the size of the + * implementation-defined type returned by bind is larger than N a + * compile-time error is generated. Also, calling an empty Callback does + * nothing, while doing the same on a function results in an exception + * being thrown. + * + * The reason why one would want to use this class is because, other than the + * limitations, this class also offers a guarantee: it will never allocate + * data on the heap. It is not just a matter of code speed: in Miosix calling + * new/delete/malloc/free from an interrupt routine produces undefined + * behaviour, so this class enables binding function calls form an interrupt + * safely. + * + * \param N the size in bytes that an instance of this class reserves to + * store the function objects. If the line starting with 'typedef char check1' + * starts failing it means it is time to increase this number. The size + * of an instance of this object is N+sizeof(void (*)()), but with N rounded + * by excess to four byte boundaries. + */ +template +class Callback : private CallbackBase +{ +public: + /** + * Default constructor. Produces an empty callback. + */ + Callback() : operation(0) {} + + /** + * Constructor. Not explicit by design. + * \param functor function object a copy of which is stored internally + */ + template + Callback(T functor) : operation(0) + { + *this=functor; + } + + /** + * Copy constructor + * \param rhs object to copy + */ + Callback(const Callback& rhs) + { + operation=rhs.operation; + if(operation) operation(any,rhs.any,ASSIGN); + } + + /** + * Operator = + * \param rhs object to copy + * \return *this + */ + Callback& operator= (const Callback& rhs); + + /** + * Assignment operation, assigns a function object to this callback. + * \param funtor function object a copy of which is stored internally + */ + template + Callback& operator= (T functor); + + /** + * Removes any function object stored in this class + */ + void clear() + { + if(operation) operation(any,0,DESTROY); + operation=0; + } + + /** + * Call the callback, or do nothing if no callback is set + */ + void operator() () + { + if(operation) operation(any,0,CALL); + } + + /** + * Call the callback, generating undefined behaviour if no callback is set + */ + void call() + { + operation(any,0,CALL); + } + + //Safe bool idiom + struct SafeBoolStruct { void* b; }; + typedef void* SafeBoolStruct::* SafeBool; + + /** + * \return true if the object contains a callback + */ + operator SafeBool() const + { + return operation==0 ? 0 : &SafeBoolStruct::b; + } + + /** + * Destructor + */ + ~Callback() + { + if(operation) operation(any,0,DESTROY); + } + +private: + /// This declaration is done like that to save memory. On 32 bit systems + /// the size of a pointer is 4 bytes, but the strictest alignment is 8 + /// which is that of double and long long. Using an array of doubles would + /// have guaranteed alignment but the array size would have been a multiple + /// of 8 bytes, and by summing the 4 bytes of the operation pointer there + /// would have been 4 bytes of slack space left unused when declaring arrays + /// of callbacks. Therefore any is declared as an array of ints but aligned + /// to 8 bytes. This allows i.e. declaring Callback<20> with 20 bytes of + /// useful storage and 4 bytes of pointer, despite 20 is not a multiple of 8 + int32_t any[(N+3)/4] __attribute__((aligned(8))); + void (*operation)(int32_t *a, const int32_t *b, Op op); +}; + +template +Callback& Callback::operator= (const Callback& rhs) +{ + if(this==&rhs) return *this; //Handle assignmento to self + if(operation) operation(any,0,DESTROY); + operation=rhs.operation; + if(operation) operation(any,rhs.any,ASSIGN); + return *this; +} + +template +template +Callback& Callback::operator= (T functor) +{ + //If an error is reported about this line an attempt to store a too large + //object is made. Increase N. + static_assert(sizeof(any)>=sizeof(T),""); + + //This should not fail unless something has a stricter alignment than double + static_assert(__alignof__(any)>=__alignof__(T),""); + + if(operation) operation(any,0,DESTROY); + + new (reinterpret_cast(any)) T(functor); + operation=TypeDependentOperation::operation; + return *this; +} + +} //namespace miosix + +#endif //CALLBACK_H diff --git a/lib/miosix-kernel/miosix/e20/e20.cpp b/lib/miosix-kernel/miosix/e20/e20.cpp new file mode 100644 index 00000000..47642394 --- /dev/null +++ b/lib/miosix-kernel/miosix/e20/e20.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** + * Copyright (C) 2012, 2013, 2014, 2015, 2016 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "e20.h" + +using namespace std; + +namespace miosix { + +// +// Class EventQueue +// + +void EventQueue::post(function event) +{ + Lock l(m); + events.push_back(event); + cv.signal(); +} + +void EventQueue::run() +{ + Lock l(m); + for(;;) + { + while(events.empty()) cv.wait(l); + function f=events.front(); + events.pop_front(); + { + Unlock u(l); + f(); + } + } +} + +void EventQueue::runOne() +{ + function f; + { + Lock l(m); + if(events.empty()) return; + f=events.front(); + events.pop_front(); + } + f(); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/e20/e20.h b/lib/miosix-kernel/miosix/e20/e20.h new file mode 100644 index 00000000..9eb191d3 --- /dev/null +++ b/lib/miosix-kernel/miosix/e20/e20.h @@ -0,0 +1,460 @@ +/*************************************************************************** + * Copyright (C) 2012, 2013, 2014, 2015, 2016 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +//Miosix event based API + +#ifndef E20_H +#define E20_H + +#include +#include +#include +#include "callback.h" + +namespace miosix { + +/** + * A variable sized event queue. + * + * Makes use of heap allocations and as such it is not possible to post events + * from within interrupt service routines. For this, use FixedEventQueue. + * + * This class acts as a synchronization point, multiple threads can post + * events, and multiple threads can call run() or runOne() (thread pooling). + * + * Events are function that are posted by a thread through post() but executed + * in the context of the thread that calls run() or runOne() + */ +class EventQueue +{ +public: + /** + * Constructor + */ + EventQueue() {} + + /** + * Post an event to the queue. This function never blocks. + * + * \param event function function to be called in the thread that calls + * run() or runOne(). Bind can be used to bind parameters to the function. + * \throws std::bad_alloc if there is not enough heap memory + */ + void post(std::function event); + + /** + * This function blocks waiting for events being posted, and when available + * it calls the event function. To return from this event loop an event + * function must throw an exception. + * + * \throws any exception that is thrown by the event functions + */ + void run(); + + /** + * Run at most one event. This function does not block. + * + * \throws any exception that is thrown by the event functions + */ + void runOne(); + + /** + * \return the number of events in the queue + */ + unsigned int size() const + { + Lock l(m); + return events.size(); + } + + /** + * \return true if the queue has no events + */ + bool empty() const + { + Lock l(m); + return events.empty(); + } + +private: + EventQueue(const EventQueue&); + EventQueue& operator= (const EventQueue&); + + std::list > events; ///< Event queue + mutable FastMutex m; ///< Mutex for synchronisation + ConditionVariable cv; ///< Condition variable for synchronisation +}; + +/** + * This class is to extract from FixedEventQueue code that + * does not depend on the NumSlots template parameters. + */ +template +class FixedEventQueueBase +{ +protected: + /** + * Constructor. + */ + FixedEventQueueBase() : put(0), get(0), n(0), waitingGet(0), waitingPut(0) + {} + + /** + * Post an event. Blocks if event queue is full. + * \param event event to post + * \param events pointer to event queue + * \param size event queue size + */ + void postImpl(Callback& event, Callback *events, + unsigned int size); + + /** + * Post an event from an interrupt, or with interrupts disabled. + * \param event event to post + * \param events pointer to event queue + * \param size event queue size + * \param hppw set to true if a higher priority thread is awakened, + * otherwise the variable is not modified + */ + bool IRQpostImpl(Callback& event, Callback *events, + unsigned int size, bool *hppw=0); + + /** + * This function blocks waiting for events being posted, and when available + * it calls the event function. To return from this event loop an event + * function must throw an exception. + * + * \param events pointer to event queue + * \param size event queue size + * \throws any exception that is thrown by the event functions + */ + void runImpl(Callback *events, unsigned int size); + + /** + * Run at most one event. This function does not block. + * + * \param events pointer to event queue + * \param size event queue size + * \throws any exception that is thrown by the event functions + */ + void runOneImpl(Callback *events, unsigned int size); + + /** + * \return the number of events in the queue + */ + unsigned int sizeImpl() const + { + FastInterruptDisableLock dLock; + return n; + } + +private: + /** + * To allow multiple threads waiting on put and get + */ + struct WaitingList + { + WaitingList *next; ///< Pointer to next element of the list + Thread *t; ///< Thread waiting + bool token; ///< To tolerate spurious wakeups + }; + + unsigned int put; ///< Put position into events + unsigned int get; ///< Get position into events + unsigned int n; ///< Number of occupied event slots + WaitingList *waitingGet; ///< List of threads waiting to get an event + WaitingList *waitingPut; ///< List of threads waiting to put an event +}; + +template +void FixedEventQueueBase::postImpl(Callback& event, + Callback *events, unsigned int size) +{ + //Not FastInterruptDisableLock as the operator= of the bound + //parameters of the Callback may allocate + InterruptDisableLock dLock; + while(n>=size) + { + WaitingList w; + w.token=false; + w.t=Thread::IRQgetCurrentThread(); + w.next=waitingPut; + waitingPut=&w; + while(w.token==false) + { + Thread::IRQwait(); + { + InterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + IRQpostImpl(event,events,size); +} + +template +bool FixedEventQueueBase::IRQpostImpl(Callback& event, + Callback *events, unsigned int size, bool *hppw) +{ + if(n>=size) return false; + events[put]=event; //This may allocate memory + if(++put>=size) put=0; + n++; + if(waitingGet) + { + Thread *t=Thread::IRQgetCurrentThread(); + if(hppw && waitingGet->t->IRQgetPriority()>t->IRQgetPriority()) + *hppw=true; + waitingGet->token=true; + waitingGet->t->IRQwakeup(); + waitingGet=waitingGet->next; + } + return true; +} + +template +void FixedEventQueueBase::runImpl(Callback *events, + unsigned int size) +{ + //Not FastInterruptDisableLock as the operator= of the bound + //parameters of the Callback may allocate + InterruptDisableLock dLock; + for(;;) + { + while(n<=0) + { + WaitingList w; + w.token=false; + w.t=Thread::IRQgetCurrentThread(); + w.next=waitingGet; + waitingGet=&w; + while(w.token==false) + { + Thread::IRQwait(); + { + InterruptEnableLock eLock(dLock); + Thread::yield(); + } + } + } + Callback f=events[get]; //This may allocate memory + if(++get>=size) get=0; + n--; + if(waitingPut) + { + waitingPut->token=true; + waitingPut->t->IRQwakeup(); + waitingPut=waitingPut->next; + } + { + InterruptEnableLock eLock(dLock); + f(); + } + } +} + +template +void FixedEventQueueBase::runOneImpl(Callback *events, + unsigned int size) +{ + Callback f; + { + //Not FastInterruptDisableLock as the operator= of the bound + //parameters of the Callback may allocate + InterruptDisableLock dLock; + if(n<=0) return; + f=events[get]; //This may allocate memory + if(++get>=size) get=0; + n--; + if(waitingPut) + { + waitingPut->token=true; + waitingPut->t->IRQwakeup(); + waitingPut=waitingPut->next; + } + } + f(); +} + +/** + * A fixed size event queue. + * + * This guarantees it makes no use of the heap, therefore events can be posted + * also from within interrupt handlers. This simplifies the development of + * device drivers. + * + * This class acts as a synchronization point, multiple threads (and IRQs) can + * post events, and multiple threads can call run() or runOne() + * (thread pooling). + * + * Events are function that are posted by a thread through post() but executed + * in the context of the thread that calls run() or runOne() + * + * \param NumSlots maximum queue length + * \param SlotSize size of the Callback objects. This limits the maximum number + * of parameters that can be bound to a function. If you get compile-time + * errors in callback.h, consider increasing this value. The default is 20 + * bytes, which is enough to bind a member function pointer, a "this" pointer + * and two byte or pointer sized parameters. + */ +template +class FixedEventQueue : private FixedEventQueueBase +{ +public: + /** + * Constructor. + */ + FixedEventQueue() {} + + /** + * Post an event, blocking if the event queue is full. + * + * \param event function function to be called in the thread that calls + * run() or runOne(). Bind can be used to bind parameters to the function. + * Unlike with the EventQueue, the operator= of the bound parameters have + * the restriction that they need to be callable from inside a + * InterruptDisableLock without causing undefined behaviour, so they + * must not, open files, print, ... but can allocate memory. + */ + void post(Callback event) + { + this->postImpl(event,events,NumSlots); + } + + /** + * Post an event in the queue, or return if the queue was full. + * + * \param event function function to be called in the thread that calls + * run() or runOne(). Bind can be used to bind parameters to the function. + * Unlike with the EventQueue, the operator= of the bound parameters have + * the restriction that they need to be callable from inside a + * InterruptDisableLock without causing undefined behaviour, so they + * must not open files, print, ... but can allocate memory. + * \return false if there was no space in the queue + */ + bool postNonBlocking(Callback event) + { + InterruptDisableLock dLock; + return this->IRQpostImpl(event,events,NumSlots); + } + + /** + * Post an event in the queue, or return if the queue was full. + * Can be called only with interrupts disabled or within an interrupt + * handler, allowing device drivers to post an event to a thread. + * + * \param event function function to be called in the thread that calls + * run() or runOne(). Bind can be used to bind parameters to the function. + * Unlike with the EventQueue, the operator= of the bound parameters have + * the restriction that they need to be callable with interrupts disabled + * so they must not open files, print, ... + * + * If the call is made from within an InterruptDisableLock the copy + * constructors can allocate memory, while if the call is made from an + * interrupt handler or a FastInterruptFisableLock memory allocation is + * forbidden. + * \return false if there was no space in the queue + */ + bool IRQpost(Callback event) + { + return this->IRQpostImpl(event,events,NumSlots); + } + + /** + * Post an event in the queue, or return if the queue was full. + * Can be called only with interrupts disabled or within an interrupt + * handler, allowing device drivers to post an event to a thread. + * + * \param event function function to be called in the thread that calls + * run() or runOne(). Bind can be used to bind parameters to the function. + * Unlike with the EventQueue, the operator= of the bound parameters have + * the restriction that they need to be callable with interrupts disabled + * so they must not open files, print, ... + * + * If the call is made from within an InterruptDisableLock the copy + * constructors can allocate memory, while if the call is made from an + * interrupt handler or a FastInterruptFisableLock memory allocation is + * forbidden. + * \param hppw returns true if a higher priority thread was awakened as + * part of posting the event. Can be used inside an IRQ to call the + * scheduler. + * \return false if there was no space in the queue + */ + bool IRQpost(Callback event, bool& hppw) + { + hppw=false; + return this->IRQpostImpl(event,events,NumSlots,&hppw); + } + + /** + * This function blocks waiting for events being posted, and when available + * it calls the event function. To return from this event loop an event + * function must throw an exception. + * + * \throws any exception that is thrown by the event functions + */ + void run() + { + this->runImpl(events,NumSlots); + } + + /** + * Run at most one event. This function does not block. + * + * \throws any exception that is thrown by the event functions + */ + void runOne() + { + this->runOneImpl(events,NumSlots); + } + + /** + * \return the number of events in the queue + */ + unsigned int size() const + { + return this->sizeImpl(); + } + + /** + * \return true if the queue has no events + */ + unsigned int empty() const + { + return this->sizeImpl()==0; + } + +private: + FixedEventQueue(const FixedEventQueue&); + FixedEventQueue& operator= (const FixedEventQueue&); + + Callback events[NumSlots]; ///< Fixed size queue of events +}; + +} //namespace miosix + +#endif //E20_H diff --git a/lib/miosix-kernel/miosix/e20/unmember.cpp b/lib/miosix-kernel/miosix/e20/unmember.cpp new file mode 100644 index 00000000..fb64f840 --- /dev/null +++ b/lib/miosix-kernel/miosix/e20/unmember.cpp @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2017 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "unmember.h" +#include + +using namespace std; + +namespace miosix { + +tuple unmemberLogic(unsigned long mixedField, + long thisOffset, unsigned long *o) noexcept +{ + //ARM stores the "virtual function or not?" bit in bit #0 of thisOffset and + //multiplies the offset by 2 to make room for it. Intel stores it in bit #0 + //of mixedField + + #ifdef __ARM_EABI__ + //With multiple or virtual inheritance we need to add an offset to this. + o+=(thisOffset & 0xfffffffe)/2/sizeof(long); + + //Then we have two cases, we can have a member function pointer to a + //virtual or nonvirtual function. For virtual functions mixedField is the + //offset in the vtable where to find the function pointer. + //For nonvirtual functions it is already the desired function pointer + void (*result)(void*); + if(thisOffset & 1) + { + //Pointer to virtual function. Dereference the object to get to the + //virtual table, and then get the function pointer at the correct offset + unsigned long *vtbl=reinterpret_cast(*o); + result=reinterpret_cast(vtbl[mixedField/sizeof(long)]); + } else { + //Pointer to non virtual function + result=reinterpret_cast(mixedField); + } + + #elif defined(__i386) || defined(__x86_64__) + //With multiple or virtual inheritance we need to add an offset to this. + o+=thisOffset/sizeof(long); + + //Then we have two cases, we can have a member function pointer to a + //virtual or nonvirtual function. For virtual functions mixedField is the + //offset in the vtable where to find the function pointer. + //For nonvirtual functions it is already the desired function pointer + void (*result)(void*); + if(mixedField & 1) + { + //Pointer to virtual function. Dereference the object to get to the + //virtual table, and then get the function pointer at the correct offset + unsigned long *vtbl=reinterpret_cast(*o); + result=reinterpret_cast(vtbl[(mixedField-1)/sizeof(long)]); + } else { + //Pointer to non virtual function + result=reinterpret_cast(mixedField); + } + #else + #error The layout of virtual function pointer is unknown for this arch + #endif + + return make_tuple(result,reinterpret_cast(o)); +} + +} //namespace miosix + +//Testsuite for member function pointer implementation. Compile with: +// g++ -std=c++11 -O2 -DTEST_ALGORITHM -o test unmember.cpp; ./test +#ifdef TEST_ALGORITHM + +#include + +using namespace std; +using namespace miosix; + +class Base +{ +public: + void m1() { printf("Base m1 %d %p\n",y,this); } + virtual void m2() { printf("Base m2 %d %p\n",y,this); } + virtual void m3() { printf("Base m3 %d %p\n",y,this); } + int y=1234; +}; + +class Base2 +{ +public: + void m4() { printf("Base2 m4 %d %p\n",x,this); } + virtual void m5() { printf("Base2 m5 %d %p\n",x,this); } + int x=5678; +}; + +class Derived : public Base +{ +public: + virtual void m3() { printf("Derived m3 %d %p\n",y,this); } +}; + +class Derived2 : public Base, public Base2 {}; + +class A { public: int a=1; }; +class B : virtual public A { public: int b=2; }; +class C : virtual public A { public: int c=3; void mf() { printf("%d\n",c); } }; +class D : public B, public C { public: int d=4; }; + +void call(tuple a) { (*get<0>(a))(get<1>(a)); } + +int main() +{ + Base b; + Derived d; + Derived2 d2; + D dd; + call(unmember(&Base::m1,&b)); + call(unmember(&Base::m2,&b)); + call(unmember(&Base::m3,&b)); + call(unmember(&Derived::m3,&d)); + call(unmember(&Derived2::m4,&d2)); + call(unmember(&Derived2::m5,&d2)); + call(unmember(&D::mf,&dd)); +} + +#endif //TEST_ALGORITHM diff --git a/lib/miosix-kernel/miosix/e20/unmember.h b/lib/miosix-kernel/miosix/e20/unmember.h new file mode 100644 index 00000000..10c85199 --- /dev/null +++ b/lib/miosix-kernel/miosix/e20/unmember.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2017 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#pragma once + +#include + +namespace miosix { + +/** + * \internal + * Template-independent code of unmember is here to reduce code size. + * Do not call this function directly + * \param mixedField either the function pointer for nonvirtual functions, + * or the vtable offset (with 1 added to disambiguate it from the previous case) + * for virtual functions + * \param thisOffset the offset to add to this for multiple/virtual inheritance + * \param o the object pointer that has to be passed as this + * \retun the ordinary function pointer and void * to call it with + */ +std::tuple unmemberLogic(unsigned long mixedField, + long thisOffset, unsigned long *o) noexcept; + +/** + * This function performs the forbidden cast of C++: casting from a member + * function pointer of any class and a class pointer to an ordinary function + * pointer and an opaque void * parameter to call it with. It allows to call + * any member function of any class (that takes no parameters and returns void) + * in an uniform and efficient way. + * + * The code is not portable, as the underlying representation of member function + * pointers is not specified by the standard. According to + * https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible + * there are multiple implementations. + * This code has been tested with the GCC and the LLVM compilers, in both + * X86, X86-64 and ARM32. + * + * \param mfn a member function pointer of any class that takes no parameters + * and returns void + * \param object an object on which the member function has to be called + * \return the ordinary function pointer and void * to call it with + */ +template +std::tuple unmember(void (T::*mfn)(), T *object) noexcept +{ + //This code only works with GCC/LLVM + #if !defined(__clang__) && !defined(__GNUC__) + #error Unknown member function pointer layout + #endif + + //A union is used to "inspect" the internals of the member function pointer + union { + void (T::*mfn)(); + struct { + unsigned long mixedField; + long thisOffset; + }; + } unpack; + unpack.mfn=mfn; + + //Unsigned long is used as its size is 4 on 32bit systems and 8 in 64bit + unsigned long *o=reinterpret_cast(object); + + return unmemberLogic(unpack.mixedField,unpack.thisOffset,o); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/console/console_device.cpp b/lib/miosix-kernel/miosix/filesystem/console/console_device.cpp new file mode 100644 index 00000000..6aadee40 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/console/console_device.cpp @@ -0,0 +1,223 @@ +/*************************************************************************** + * Copyright (C) 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "console_device.h" +#include "filesystem/ioctl.h" +#include +#include + +using namespace std; + +namespace miosix { + +// +// class TerminalDevice +// + +TerminalDevice::TerminalDevice(intrusive_ref_ptr device) + : FileBase(intrusive_ref_ptr()), device(device), + mutex(), echo(true), binary(false), skipNewline(false) {} + +ssize_t TerminalDevice::write(const void *data, size_t length) +{ + if(binary) return device->writeBlock(data,length,0); + //No mutex here to avoid blocking writes while reads are in progress + const char *buffer=static_cast(data); + const char *start=buffer; + //Try to write data in chunks, stop at every \n to replace with \r\n + //Although it may be tempting to call echoBack() from here since it performs + //a similar task, it is not possible, as echoBack() uses a class field, + //chunkStart, and write is not mutexed to allow concurrent writing + for(size_t i=0;istart) + { + ssize_t r=device->writeBlock(start,buffer-start,0); + if(r<=0) return r; + } + ssize_t r=device->writeBlock("\r\n",2,0);//Add \r\n + if(r<=0) return r; + start=buffer+1; + } + if(buffer>start) + { + ssize_t r=device->writeBlock(start,buffer-start,0); + if(r<=0) return r; + } + return length; +} + +ssize_t TerminalDevice::read(void *data, size_t length) +{ + if(binary) + { + ssize_t result=device->readBlock(data,length,0); + if(echo && result>0) device->writeBlock(data,result,0);//Ignore write errors + return result; + } + Lock l(mutex); //Reads are serialized + char *buffer=static_cast(data); + size_t readBytes=0; + for(;;) + { + ssize_t r=device->readBlock(buffer+readBytes,length-readBytes,0); + if(r<0) return r; + pair result=normalize(buffer,readBytes,readBytes+r); + readBytes=result.first; + if(readBytes==length || result.second) return readBytes; + } +} + +#ifdef WITH_FILESYSTEM + +off_t TerminalDevice::lseek(off_t pos, int whence) +{ + return -EBADF; +} + +int TerminalDevice::fstat(struct stat *pstat) const +{ + return device->fstat(pstat); +} + +int TerminalDevice::isatty() const { return device->isatty(); } + +#endif //WITH_FILESYSTEM + +int TerminalDevice::ioctl(int cmd, void *arg) +{ + if(int result=device->ioctl(cmd,arg)!=0) return result; + termios *t=reinterpret_cast(arg); + switch(cmd) + { + case IOCTL_TCGETATTR: + if(echo) t->c_lflag |= ECHO; else t->c_lflag &= ~ECHO; + if(binary==false) t->c_lflag |= ICANON; else t->c_lflag &= ~ICANON; + break; + case IOCTL_TCSETATTR_NOW: + case IOCTL_TCSETATTR_DRAIN: + case IOCTL_TCSETATTR_FLUSH: + echo=(t->c_lflag & ECHO) ? true : false; + binary=(t->c_lflag & ICANON) ? false : true; + break; + default: + break; + } + return 0; +} + +pair TerminalDevice::normalize(char *buffer, ssize_t begin, + ssize_t end) +{ + bool newlineFound=false; + buffer+=begin; + chunkStart=buffer; + for(ssize_t i=begin;ichunkStart) device->writeBlock(chunkStart,chunkEnd-chunkStart,0); + chunkStart=chunkEnd+1; + if(sep) device->writeBlock(sep,sepLen,0); //Ignore write errors +} + +// +// class DefaultConsole +// + +DefaultConsole& DefaultConsole::instance() +{ + static DefaultConsole singleton; + return singleton; +} + +void DefaultConsole::IRQset(intrusive_ref_ptr console) +{ + //Note: should be safe to be called also outside of IRQ as set() calls + //IRQset() + atomic_store(&this->console,console); + #ifndef WITH_FILESYSTEM + atomic_store(&terminal, + intrusive_ref_ptr(new TerminalDevice(console))); + #endif //WITH_FILESYSTEM +} + +DefaultConsole::DefaultConsole() : console(new Device(Device::STREAM)) +#ifndef WITH_FILESYSTEM +, terminal(new TerminalDevice(console)) +#endif //WITH_FILESYSTEM +{} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/console/console_device.h b/lib/miosix-kernel/miosix/filesystem/console/console_device.h new file mode 100644 index 00000000..7a20b4d0 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/console/console_device.h @@ -0,0 +1,228 @@ +/*************************************************************************** + * Copyright (C) 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef CONSOLE_DEVICE_H +#define CONSOLE_DEVICE_H + +#include "config/miosix_settings.h" +#include "filesystem/devfs/devfs.h" +#include "kernel/sync.h" + +namespace miosix { + +/** + * Teriminal device, proxy object supporting additional terminal-specific + * features + */ +class TerminalDevice : public FileBase +{ +public: + /** + * Constructor + * \param device proxed device. + */ + TerminalDevice(intrusive_ref_ptr device); + + /** + * Write data to the file, if the file supports writing. + * \param data the data to write + * \param length the number of bytes to write + * \return the number of written characters, or a negative number in case + * of errors + */ + virtual ssize_t write(const void *data, size_t length); + + /** + * Read data from the file, if the file supports reading. + * \param data buffer to store read data + * \param length the number of bytes to read + * \return the number of read characters, or a negative number in case + * of errors + */ + virtual ssize_t read(void *data, size_t length); + + #ifdef WITH_FILESYSTEM + + /** + * Move file pointer, if the file supports random-access. + * \param pos offset to sum to the beginning of the file, current position + * or end of file, depending on whence + * \param whence SEEK_SET, SEEK_CUR or SEEK_END + * \return the offset from the beginning of the file if the operation + * completed, or a negative number in case of errors + */ + virtual off_t lseek(off_t pos, int whence); + + /** + * Return file information. + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + virtual int fstat(struct stat *pstat) const; + + /** + * Check whether the file refers to a terminal. + * \return 1 if it is a terminal, 0 if it is not, or a negative number in + * case of errors + */ + virtual int isatty() const; + + #endif //WITH_FILESYSTEM + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + virtual int ioctl(int cmd, void *arg); + + /** + * Enables or disables echo of commands on the terminal + * \param echo true to enable echo, false to disable it + */ + void setEcho(bool echoMode) { echo=echoMode; } + + /** + * \return true if echo is enabled + */ + bool isEchoEnabled() const { return echo; } + + /** + * Selects whether the terminal sholud be transparent to non ASCII data + * \param rawMode true if raw mode is required + */ + void setBinary(bool binaryMode) { binary=binaryMode; } + + /** + * \return true if the terminal allows binary data + */ + bool isBinary() const { return binary; } + +private: + /** + * Perform normalization of a read buffer (\r\n conversion to \n, backspace) + * \param buffer pointer to read buffer + * \param begin buffer[begin] is the first character to normalize + * \param end buffer[end] is one past the las character to normalize + * \return a pair with the number of valid character in the buffer (staring + * from buffer[0], not from buffer[begin], and a bool that is true if at + * least one \n was found. + */ + std::pair normalize(char *buffer, ssize_t begin, ssize_t end); + + /** + * Perform echo when reading a buffer + * \param chunkEnd one past the last character to echo back. The first + * character is chunkStart. As a side effect, this member function modifies + * chunkStart to be equal to chunkEnd+1, if echo is enabled + * \param sep optional line separator, printed after the chunk + * \param sepLen separator length + */ + void echoBack(const char *chunkEnd, const char *sep=0, size_t sepLen=0); + + intrusive_ref_ptr device; ///< Underlying TTY device + FastMutex mutex; ///< Mutex to serialze concurrent reads + const char *chunkStart; ///< First character to echo in echoBack() + bool echo; ///< True if echo enabled + bool binary; ///< True if binary mode enabled + bool skipNewline; ///< Used by normalize() +}; + +/** + * This class holds the file object related to the console, that is set by + * the board support package, and used to populate /dev/console in DevFs + */ +class DefaultConsole +{ +public: + /** + * \return an instance of this class (singleton) + */ + static DefaultConsole& instance(); + + /** + * Called by the board support package, in particular IRQbspInit(), to pass + * to the kernel the console device. This device file is used as the default + * one for stdin/stdout/stderr. + * Notes: this has to be called in IRQbspInit(), since if it's called too + * late the console gets initialized with a NullFile. + * Also, calling this a second time to dynamically change the console device + * is probably a bad idea, as the device is cached around in the filesystem + * code and will result in some processes using the old device and some + * other the new one. + * \param console device file handling console I/O. Can only be called with + * interrupts disabled. + */ + void IRQset(intrusive_ref_ptr console); + + /** + * Same as IRQset(), but can be called with interrupts enabled + * \param console device file handling console I/O. Can only be called with + * interrupts disabled. + */ + void set(intrusive_ref_ptr console) { IRQset(console); } + + /** + * \return the currently installed console device, wrapped in a + * TerminalDevice + */ + intrusive_ref_ptr get() { return console; } + + /** + * \return the currently installed console device. + * Can be called with interrupts disabled or within an interrupt routine. + */ + intrusive_ref_ptr IRQget() { return console; } + + #ifndef WITH_FILESYSTEM + /** + * \return the terminal device, when filesystem support is disabled. + * If filesystem is enabled, the terminal device can be found in the + * FileDescriptorTable + */ + intrusive_ref_ptr getTerminal() { return terminal; } + #endif //WITH_FILESYSTEM + +private: + /** + * Constructor, private as it is a singleton + */ + DefaultConsole(); + + DefaultConsole(const DefaultConsole&); + DefaultConsole& operator= (const DefaultConsole&); + + intrusive_ref_ptr console; ///< The raw console device + #ifndef WITH_FILESYSTEM + intrusive_ref_ptr terminal; ///< The wrapped console device + #endif //WITH_FILESYSTEM +}; + +} //namespace miosix + +#endif //CONSOLE_DEVICE_H diff --git a/lib/miosix-kernel/miosix/filesystem/devfs/devfs.cpp b/lib/miosix-kernel/miosix/filesystem/devfs/devfs.cpp new file mode 100644 index 00000000..2734941e --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/devfs/devfs.cpp @@ -0,0 +1,405 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "devfs.h" +#include +#include +#include +#include "filesystem/stringpart.h" + +using namespace std; + +namespace miosix { + +static const int _NOSEEK=0x20000; //Special flag used only here to disallow seek + +static void fillStatHelper(struct stat* pstat, unsigned int st_ino, + short st_dev, mode_t mode) +{ + memset(pstat,0,sizeof(struct stat)); + pstat->st_dev=st_dev; + pstat->st_ino=st_ino; + pstat->st_mode=mode; + pstat->st_nlink=1; + pstat->st_blksize=0; //If zero means file buffer equals to BUFSIZ +} + +/** + * This file type is for reading and writing from devices + */ +class DevFsFile : public FileBase +{ +public: + /** + * Constructor + * \param fs pointer to DevFs + * \param dev the device to which this file refers + * \param flags file open flags (_FREAD, _FWRITE, ...) + */ + DevFsFile(intrusive_ref_ptr fs, + intrusive_ref_ptr dev, int flags) : FileBase(fs), + dev(dev), seekPoint(0), flags(flags) {} + + /** + * Write data to the file, if the file supports writing. + * \param data the data to write + * \param len the number of bytes to write + * \return the number of written characters, or a negative number in + * case of errors + */ + virtual ssize_t write(const void *data, size_t len); + + /** + * Read data from the file, if the file supports reading. + * \param data buffer to store read data + * \param len the number of bytes to read + * \return the number of read characters, or a negative number in + * case of errors + */ + virtual ssize_t read(void *data, size_t len); + + /** + * Move file pointer, if the file supports random-access. + * \param pos offset to sum to the beginning of the file, current position + * or end of file, depending on whence + * \param whence SEEK_SET, SEEK_CUR or SEEK_END + * \return the offset from the beginning of the file if the operation + * completed, or a negative number in case of errors + */ + virtual off_t lseek(off_t pos, int whence); + + /** + * Return file information. + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + virtual int fstat(struct stat *pstat) const; + + /** + * Check whether the file refers to a terminal. + * \return 1 if it is a terminal, 0 if it is not, or a negative number in + * case of errors + */ + virtual int isatty() const; + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + virtual int ioctl(int cmd, void *arg); + +private: + intrusive_ref_ptr dev; ///< Device file + off_t seekPoint; ///< Seek point (note that off_t is 64bit) + int flags; ///< File open flags +}; + +ssize_t DevFsFile::write(const void *data, size_t len) +{ + if((flags & _FWRITE)==0) return -EINVAL; + if(seekPoint+static_cast(len)<0) + len=numeric_limits::max()-seekPoint-len; + ssize_t result=dev->writeBlock(data,len,seekPoint); + if(result>0 && ((flags & _NOSEEK)==0)) seekPoint+=result; + return result; +} + +ssize_t DevFsFile::read(void *data, size_t len) +{ + if((flags & _FREAD)==0) return -EINVAL; + if(seekPoint+static_cast(len)<0) + len=numeric_limits::max()-seekPoint-len; + ssize_t result=dev->readBlock(data,len,seekPoint); + if(result>0 && ((flags & _NOSEEK)==0)) seekPoint+=result; + return result; +} + +off_t DevFsFile::lseek(off_t pos, int whence) +{ + if(flags & _NOSEEK) return -EBADF; //No seek support + + off_t newSeekPoint=seekPoint; + switch(whence) + { + case SEEK_CUR: + newSeekPoint+=pos; + break; + case SEEK_SET: + newSeekPoint=pos; + break; + default: + return -EINVAL; //TODO: how to implement SEEK_END? + } + if(newSeekPoint<0) return -EOVERFLOW; + seekPoint=newSeekPoint; + return seekPoint; +} + +int DevFsFile::fstat(struct stat *pstat) const +{ + return dev->fstat(pstat); +} + +int DevFsFile::isatty() const +{ + return dev->isatty(); +} + +int DevFsFile::ioctl(int cmd, void *arg) +{ + return dev->ioctl(cmd,arg); +} + +// +// class Device +// + +int Device::open(intrusive_ref_ptr& file, + intrusive_ref_ptr fs, int flags, int mode) +{ + flags++; //To convert from O_RDONLY, O_WRONLY, ... to _FREAD, _FWRITE, ... + file=intrusive_ref_ptr( + new DevFsFile(fs,shared_from_this(),flags | (seekable ? 0 : _NOSEEK))); + return 0; +} + +int Device::fstat(struct stat* pstat) const +{ + mode_t mode=(block ? S_IFBLK : S_IFCHR) | 0750;//brwxr-x--- | crwxr-x--- + fillStatHelper(pstat,st_ino,st_dev,mode); + return 0; +} + +int Device::isatty() const +{ + return tty ? 1 : 0; +} + +ssize_t Device::readBlock(void *buffer, size_t size, off_t where) +{ + memset(buffer,0,size); //Act as /dev/zero + return size; +} + +ssize_t Device::writeBlock(const void *buffer, size_t size, off_t where) +{ + return size; //Act as /dev/null +} + +void Device::IRQwrite(const char *str) {} + +int Device::ioctl(int cmd, void *arg) +{ + return -ENOTTY; //Means the operation does not apply to this descriptor +} + +Device::~Device() {} + +#ifdef WITH_DEVFS + +/** + * Directory class for DevFs + */ +class DevFsDirectory : public DirectoryBase +{ +public: + /** + * \param parent parent filesystem + * \param mutex mutex to lock when accessing the file map + * \param files file map + * \param currentInode inode of the directory we're listing + * \param parentInode inode of the parent directory + */ + DevFsDirectory(intrusive_ref_ptr parent, + FastMutex& mutex, + map >& files, + int currentInode, int parentInode) + : DirectoryBase(parent), mutex(mutex), files(files), + currentInode(currentInode), parentInode(parentInode), + first(true), last(false) + { + Lock l(mutex); + if(files.empty()==false) currentItem=files.begin()->first.c_str(); + } + + /** + * Also directories can be opened as files. In this case, this system + * call allows to retrieve directory entries. + * \param dp pointer to a memory buffer where one or more struct dirent + * will be placed. dp must be four words aligned. + * \param len memory buffer size. + * \return the number of bytes read on success, or a negative number on + * failure. + */ + virtual int getdents(void *dp, int len); + +private: + FastMutex& mutex; ///< Mutex of parent class + map >& files; ///< Directory entries + string currentItem; ///< First unhandled item in directory + int currentInode,parentInode; ///< Inodes of . and .. + + bool first; ///< True if first time getdents is called + bool last; ///< True if directory has ended +}; + +int DevFsDirectory::getdents(void *dp, int len) +{ + if(len l(mutex); + char *begin=reinterpret_cast(dp); + char *buffer=begin; + char *end=buffer+len; + if(first) + { + first=false; + addDefaultEntries(&buffer,currentInode,parentInode); + } + if(currentItem.empty()==false) + { + map >::iterator it; + it=files.find(StringPart(currentItem)); + //Someone deleted the exact directory entry we had saved (unlikely) + if(it==files.end()) return -EBADF; + for(;it!=files.end();++it) + { + struct stat st; + it->second->fstat(&st); + if(addEntry(&buffer,end,st.st_ino,st.st_mode>>12,it->first)>0) + continue; + //Buffer finished + currentItem=it->first.c_str(); + return buffer-begin; + } + } + addTerminatingEntry(&buffer,end); + last=true; + return buffer-begin; +} + +// +// class DevFs +// + +DevFs::DevFs() : mutex(FastMutex::RECURSIVE), inodeCount(rootDirInode+1) +{ + addDevice("null",intrusive_ref_ptr(new Device(Device::STREAM))); + addDevice("zero",intrusive_ref_ptr(new Device(Device::STREAM))); +} + +bool DevFs::addDevice(const char *name, intrusive_ref_ptr dev) +{ + if(name==0 || name[0]=='\0') return false; + int len=strlen(name); + for(int i=0;i l(mutex); + bool result=files.insert(make_pair(StringPart(name),dev)).second; + //Assign inode to the file + if(result) dev->setFileInfo(atomicAddExchange(&inodeCount,1),filesystemId); + return result; +} + +bool DevFs::remove(const char* name) +{ + if(name==0 || name[0]=='\0') return false; + Lock l(mutex); + map >::iterator it; + it=files.find(StringPart(name)); + if(it==files.end()) return false; + files.erase(StringPart(name)); + return true; +} + +int DevFs::open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode) +{ + if(flags & (O_APPEND | O_EXCL)) return -EACCES; + Lock l(mutex); + if(name.empty()) //Trying to open the root directory of the fs + { + if(flags & (O_WRONLY | O_RDWR)) return -EACCES; + file=intrusive_ref_ptr( + new DevFsDirectory(shared_from_this(), + mutex,files,rootDirInode,parentFsMountpointInode)); + return 0; + } + map >::iterator it=files.find(name); + if(it==files.end()) return -ENOENT; + return it->second->open(file,shared_from_this(),flags,mode); +} + +int DevFs::lstat(StringPart& name, struct stat *pstat) +{ + Lock l(mutex); + if(name.empty()) + { + fillStatHelper(pstat,rootDirInode,filesystemId,S_IFDIR | 0755);//drwxr-xr-x + return 0; + } + map >::iterator it=files.find(name); + if(it==files.end()) return -ENOENT; + return it->second->fstat(pstat); +} + +int DevFs::unlink(StringPart& name) +{ + Lock l(mutex); + if(files.erase(name)==1) return 0; + return -ENOENT; +} + +int DevFs::rename(StringPart& oldName, StringPart& newName) +{ + Lock l(mutex); + map >::iterator it=files.find(oldName); + if(it==files.end()) return -ENOENT; + for(unsigned int i=0;isecond)); + files.erase(it); + return 0; +} + +int DevFs::mkdir(StringPart& name, int mode) +{ + return -EACCES; // No directories support in DevFs yet +} + +int DevFs::rmdir(StringPart& name) +{ + return -EACCES; // No directories support in DevFs yet +} + +#endif //WITH_DEVFS + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/devfs/devfs.h b/lib/miosix-kernel/miosix/filesystem/devfs/devfs.h new file mode 100644 index 00000000..51cfd04d --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/devfs/devfs.h @@ -0,0 +1,272 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef DEVFS_H +#define DEVFS_H + +#include +#include "filesystem/file.h" +#include "filesystem/stringpart.h" +#include "kernel/sync.h" +#include "config/miosix_settings.h" + +namespace miosix { + +/** + * Instances of this class are devices inside DevFs. When open is called, a + * DevFsFile is returned, which has its own seek point so that multiple files + * can be opened on the same device retaining an unique seek point. A DevFsFile + * then calls readBlock() and writeBlock() on this class. These functions have a + * third argument which is the seek point, making them stateless. + * + * Individual devices must subclass Device and reimplement readBlock(), + * writeBlock() and ioctl() as needed. A mutex may be required as multiple + * concurrent readBlock(), writeBlock() and ioctl() can occur. + * + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + * + * This class is defined also if WITH_DEVFS is not defined as it is used by the + * Console interface, but in this case the interface is reduced to a minimum + */ +class Device : public IntrusiveRefCounted, + public IntrusiveRefCountedSharedFromThis +{ +public: + /** + * Possible device types + */ + enum DeviceType + { + STREAM, ///< Not seekable device, like /dev/random + BLOCK, ///< Seekable block device + TTY ///< Like STREAM, but additionally is a TTY + }; + /** + * Constructor + * \param d device type + */ + Device(DeviceType d) : seekable(d==BLOCK), block(d==BLOCK), tty(d==TTY) + {} + + /** + * Return an instance of the file type managed by this Device + * \param file the file object will be stored here, if the call succeeds + * \param fs pointer to the DevFs + * \param flags file flags (open for reading, writing, ...) + * \param mode file permissions + * \return 0 on success, or a negative number on failure + */ + int open(intrusive_ref_ptr& file, + intrusive_ref_ptr fs, int flags, int mode); + + /** + * Obtain information for the file type managed by this Device + * \param pstat file information is stored here + * \return 0 on success, or a negative number on failure + */ + int fstat(struct stat *pstat) const; + + /** + * Check whether the file refers to a terminal. + * \return 1 if it is a terminal, 0 if it is not, or a negative number in + * case of errors + */ + virtual int isatty() const; + + #ifdef WITH_DEVFS + + /** + * \internal + * Called be DevFs to assign a device and inode to the Device + */ + void setFileInfo(unsigned int st_ino, short st_dev) + { + this->st_ino=st_ino; + this->st_dev=st_dev; + } + + #endif //WITH_DEVFS + + /** + * Read a block of data + * \param buffer buffer where read data will be stored + * \param size buffer size + * \param where where to read from + * \return number of bytes read or a negative number on failure + */ + virtual ssize_t readBlock(void *buffer, size_t size, off_t where); + + /** + * Write a block of data + * \param buffer buffer where take data to write + * \param size buffer size + * \param where where to write to + * \return number of bytes written or a negative number on failure + */ + virtual ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + /** + * Write a string. + * An extension to the Device interface that adds a new member function, + * which is used by the kernel on console devices to write debug information + * before the kernel is started or in case of serious errors, right before + * rebooting. + * Can ONLY be called when the kernel is not yet started, paused or within + * an interrupt. This default implementation ignores writes. + * \param str the string to write. The string must be NUL terminated. + */ + virtual void IRQwrite(const char *str); + + /** + * Performs device-specific operations + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + virtual int ioctl(int cmd, void *arg); + + /** + * Destructor + */ + virtual ~Device(); + +protected: + unsigned int st_ino; ///< inode of device file + short st_dev; ///< device (unique id of the filesystem) of device file + const bool seekable; ///< If true, device is seekable + const bool block; ///< If true, it is a block device + const bool tty; ///< If true, it is a tty +}; + +#ifdef WITH_DEVFS + +/** + * DevFs is a special filesystem meant to access devices as they were files. + * For this reason, it is a little different from other filesystems. Normal + * filesystems create FileBase objects ondemand, to answer an open() call. Such + * files have a parent pointer that points to the filesystem. On the contrary, + * DevFs is a collection of both pre-existing DeviceFiles (for stateless files), + * or DeviceFileGenerators for stateful ones. Each device file is a different + * subclass of FileBase that overrides some of its member functions to access + * the handled device. These FileBase subclasses do not have a parent pointer + * into DevFs, and as such umounting DevFs should better be avoided, as it's + * not possible to detect if some of its files are currently opened by some + * application. What will happen is that the individual files (and + * DeviceFileGenerators) won't be deleted until the processes that have them + * opened close them. + */ +class DevFs : public FilesystemBase +{ +public: + /** + * Constructor + */ + DevFs(); + + /** + * Add a device file to DevFs + * \param name File name, must not start with a slash + * \param df Device file. Every open() call will return the same file + * \return true if the file was successfully added + */ + bool addDevice(const char *name, intrusive_ref_ptr dev); + + /** + * Remove a device. This prevents the device from being opened again, + * but if at the time this member function is called the file is already + * opened, it won't be deallocated till the application closes it, thanks + * to the reference counting scheme. + * \param name name of file to remove + * \return true if the file was successfully removed + */ + bool remove(const char *name); + + /** + * Open a file + * \param file the file object will be stored here, if the call succeeds + * \param name the name of the file to open, relative to the local + * filesystem + * \param flags file flags (open for reading, writing, ...) + * \param mode file permissions + * \return 0 on success, or a negative number on failure + */ + virtual int open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode); + + /** + * Obtain information on a file, identified by a path name. Does not follow + * symlinks + * \param name path name, relative to the local filesystem + * \param pstat file information is stored here + * \return 0 on success, or a negative number on failure + */ + virtual int lstat(StringPart& name, struct stat *pstat); + + /** + * Remove a file or directory + * \param name path name of file or directory to remove + * \return 0 on success, or a negative number on failure + */ + virtual int unlink(StringPart& name); + + /** + * Rename a file or directory + * \param oldName old file name + * \param newName new file name + * \return 0 on success, or a negative number on failure + */ + virtual int rename(StringPart& oldName, StringPart& newName); + + /** + * Create a directory + * \param name directory name + * \param mode directory permissions + * \return 0 on success, or a negative number on failure + */ + virtual int mkdir(StringPart& name, int mode); + + /** + * Remove a directory if empty + * \param name directory name + * \return 0 on success, or a negative number on failure + */ + virtual int rmdir(StringPart& name); + +private: + + FastMutex mutex; + std::map > files; + int inodeCount; + static const int rootDirInode=1; +}; + +#endif //WITH_DEVFS + +} //namespace miosix + +#endif //DEVFS_H diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/ccsbcs.cpp b/lib/miosix-kernel/miosix/filesystem/fat32/ccsbcs.cpp new file mode 100644 index 00000000..3f9cc272 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/ccsbcs.cpp @@ -0,0 +1,530 @@ +/*------------------------------------------------------------------------*/ +/* Unicode - Local code bidirectional converter (C)ChaN, 2012 */ +/* (SBCS code pages) */ +/*------------------------------------------------------------------------*/ +/* 437 U.S. (OEM) +/ 720 Arabic (OEM) +/ 1256 Arabic (Windows) +/ 737 Greek (OEM) +/ 1253 Greek (Windows) +/ 1250 Central Europe (Windows) +/ 775 Baltic (OEM) +/ 1257 Baltic (Windows) +/ 850 Multilingual Latin 1 (OEM) +/ 852 Latin 2 (OEM) +/ 1252 Latin 1 (Windows) +/ 855 Cyrillic (OEM) +/ 1251 Cyrillic (Windows) +/ 866 Russian (OEM) +/ 857 Turkish (OEM) +/ 1254 Turkish (Windows) +/ 858 Multilingual Latin 1 + Euro (OEM) +/ 862 Hebrew (OEM) +/ 1255 Hebrew (Windows) +/ 874 Thai (OEM, Windows) +/ 1258 Vietnam (OEM, Windows) +*/ + +#include "ff.h" +#include "config/miosix_settings.h" + +#ifdef WITH_FILESYSTEM + +#if _CODE_PAGE == 437 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP437(0x80-0xFF) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, + 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, + 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, + 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 720 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP720(0x80-0xFF) to Unicode conversion table */ + 0x0000, 0x0000, 0x00E9, 0x00E2, 0x0000, 0x00E0, 0x0000, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0651, 0x0652, 0x00F4, 0x00A4, 0x0640, 0x00FB, 0x00F9, + 0x0621, 0x0622, 0x0623, 0x0624, 0x00A3, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0636, 0x0637, 0x0638, 0x0639, 0x063A, 0x0641, 0x00B5, 0x0642, + 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, + 0x2261, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, 0x0650, 0x2248, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 737 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP737(0x80-0xFF) to Unicode conversion table */ + 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, + 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, + 0x03A1, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, + 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, + 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, + 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03C9, 0x03AC, 0x03AD, 0x03AE, 0x03CA, 0x03AF, 0x03CC, 0x03CD, + 0x03CB, 0x03CE, 0x0386, 0x0388, 0x0389, 0x038A, 0x038C, 0x038E, + 0x038F, 0x00B1, 0x2265, 0x2264, 0x03AA, 0x03AB, 0x00F7, 0x2248, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 775 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP775(0x80-0xFF) to Unicode conversion table */ + 0x0106, 0x00FC, 0x00E9, 0x0101, 0x00E4, 0x0123, 0x00E5, 0x0107, + 0x0142, 0x0113, 0x0156, 0x0157, 0x012B, 0x0179, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x014D, 0x00F6, 0x0122, 0x00A2, 0x015A, + 0x015B, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x00A4, + 0x0100, 0x012A, 0x00F3, 0x017B, 0x017C, 0x017A, 0x201D, 0x00A6, + 0x00A9, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x0141, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0104, 0x010C, 0x0118, + 0x0116, 0x2563, 0x2551, 0x2557, 0x255D, 0x012E, 0x0160, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0172, 0x016A, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x017D, + 0x0105, 0x010D, 0x0119, 0x0117, 0x012F, 0x0161, 0x0173, 0x016B, + 0x017E, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x00D3, 0x00DF, 0x014C, 0x0143, 0x00F5, 0x00D5, 0x00B5, 0x0144, + 0x0136, 0x0137, 0x013B, 0x013C, 0x0146, 0x0112, 0x0145, 0x2019, + 0x00AD, 0x00B1, 0x201C, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x201E, + 0x00B0, 0x2219, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 850 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP850(0x80-0xFF) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, + 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, + 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, + 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x0131, 0x00CD, 0x00CE, + 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, + 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, + 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 852 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP852(0x80-0xFF) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x016F, 0x0107, 0x00E7, + 0x0142, 0x00EB, 0x0150, 0x0151, 0x00EE, 0x0179, 0x00C4, 0x0106, + 0x00C9, 0x0139, 0x013A, 0x00F4, 0x00F6, 0x013D, 0x013E, 0x015A, + 0x015B, 0x00D6, 0x00DC, 0x0164, 0x0165, 0x0141, 0x00D7, 0x010D, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x0104, 0x0105, 0x017D, 0x017E, + 0x0118, 0x0119, 0x00AC, 0x017A, 0x010C, 0x015F, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x011A, + 0x015E, 0x2563, 0x2551, 0x2557, 0x255D, 0x017B, 0x017C, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x0102, 0x0103, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x0111, 0x0110, 0x010E, 0x00CB, 0x010F, 0x0147, 0x00CD, 0x00CE, + 0x011B, 0x2518, 0x250C, 0x2588, 0x2584, 0x0162, 0x016E, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x0143, 0x0144, 0x0148, 0x0160, 0x0161, + 0x0154, 0x00DA, 0x0155, 0x0170, 0x00FD, 0x00DD, 0x0163, 0x00B4, + 0x00AD, 0x02DD, 0x02DB, 0x02C7, 0x02D8, 0x00A7, 0x00F7, 0x00B8, + 0x00B0, 0x00A8, 0x02D9, 0x0171, 0x0158, 0x0159, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 855 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP855(0x80-0xFF) to Unicode conversion table */ + 0x0452, 0x0402, 0x0453, 0x0403, 0x0451, 0x0401, 0x0454, 0x0404, + 0x0455, 0x0405, 0x0456, 0x0406, 0x0457, 0x0407, 0x0458, 0x0408, + 0x0459, 0x0409, 0x045A, 0x040A, 0x045B, 0x040B, 0x045C, 0x040C, + 0x045E, 0x040E, 0x045F, 0x040F, 0x044E, 0x042E, 0x044A, 0x042A, + 0x0430, 0x0410, 0x0431, 0x0411, 0x0446, 0x0426, 0x0434, 0x0414, + 0x0435, 0x0415, 0x0444, 0x0424, 0x0433, 0x0413, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x0445, 0x0425, 0x0438, + 0x0418, 0x2563, 0x2551, 0x2557, 0x255D, 0x0439, 0x0419, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x043A, 0x041A, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x043B, 0x041B, 0x043C, 0x041C, 0x043D, 0x041D, 0x043E, 0x041E, + 0x043F, 0x2518, 0x250C, 0x2588, 0x2584, 0x041F, 0x044F, 0x2580, + 0x042F, 0x0440, 0x0420, 0x0441, 0x0421, 0x0442, 0x0422, 0x0443, + 0x0423, 0x0436, 0x0416, 0x0432, 0x0412, 0x044C, 0x042C, 0x2116, + 0x00AD, 0x044B, 0x042B, 0x0437, 0x0417, 0x0448, 0x0428, 0x044D, + 0x042D, 0x0449, 0x0429, 0x0447, 0x0427, 0x00A7, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 857 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP857(0x80-0xFF) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0131, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, + 0x0130, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x015E, 0x015F, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x011E, 0x011F, + 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, + 0x00A9, 0x2563, 0x2551, 0x2557, 0x255D, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00BA, 0x00AA, 0x00CA, 0x00CB, 0x00C8, 0x0000, 0x00CD, 0x00CE, + 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x0000, + 0x00D7, 0x00DA, 0x00DB, 0x00D9, 0x00EC, 0x00FF, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x0000, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, + 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 858 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP858(0x80-0xFF) to Unicode conversion table */ + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, + 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, + 0x00FF, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x00D7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, + 0x00BF, 0x00AE, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00C1, 0x00C2, 0x00C0, + 0x00A9, 0x2563, 0x2551, 0x2557, 0x2550, 0x00A2, 0x00A5, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x00E3, 0x00C3, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x00A4, + 0x00F0, 0x00D0, 0x00CA, 0x00CB, 0x00C8, 0x20AC, 0x00CD, 0x00CE, + 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00C6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, 0x00FE, + 0x00DE, 0x00DA, 0x00DB, 0x00D9, 0x00FD, 0x00DD, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, 0x2017, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, + 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 862 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP862(0x80-0xFF) to Unicode conversion table */ + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, + 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, + 0x05E8, 0x05E9, 0x05EA, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, + 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, + 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, + 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 866 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP866(0x80-0xFF) to Unicode conversion table */ + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, + 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, + 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, + 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, + 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x0401, 0x0451, 0x0404, 0x0454, 0x0407, 0x0457, 0x040E, 0x045E, + 0x00B0, 0x2219, 0x00B7, 0x221A, 0x2116, 0x00A4, 0x25A0, 0x00A0 +}; + +#elif _CODE_PAGE == 874 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP874(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x0000, 0x0000, 0x0000, 0x2026, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00A0, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07, + 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F, + 0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, + 0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F, + 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27, + 0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F, + 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37, + 0x0E38, 0x0E39, 0x0E3A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0E3F, + 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47, + 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F, + 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, + 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, 0x0000, 0x0000, 0x0000, 0x0000 +}; + +#elif _CODE_PAGE == 1250 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1250(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, + 0x0000, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, + 0x00A0, 0x02C7, 0x02D8, 0x0141, 0x00A4, 0x0104, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B, + 0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x0105, 0x015F, 0x00BB, 0x013D, 0x02DD, 0x013E, 0x017C, + 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, + 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, + 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, + 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, + 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, + 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9 +}; + +#elif _CODE_PAGE == 1251 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1251(0x80-0xFF) to Unicode conversion table */ + 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021, + 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F, + 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x0000, 0x2111, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F, + 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7, + 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407, + 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7, + 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F +}; + +#elif _CODE_PAGE == 1252 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1252(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x017D, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0000, 0x017E, 0x0178, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF +}; + +#elif _CODE_PAGE == 1253 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1253(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x0000, 0x2030, 0x0000, 0x2039, 0x000C, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x0000, 0x2122, 0x0000, 0x203A, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00A0, 0x0385, 0x0386, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x0000, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x2015, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x00B5, 0x00B6, 0x00B7, + 0x0388, 0x0389, 0x038A, 0x00BB, 0x038C, 0x00BD, 0x038E, 0x038F, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + 0x03A0, 0x03A1, 0x0000, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, + 0x03A8, 0x03A9, 0x03AA, 0x03AD, 0x03AC, 0x03AD, 0x03AE, 0x03AF, + 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x0000 +}; + +#elif _CODE_PAGE == 1254 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1254(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x210A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0000, 0x0000, 0x0178, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00BD, 0x00DC, 0x0130, 0x015E, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF +}; + +#elif _CODE_PAGE == 1255 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1255(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0000, 0x2039, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0000, 0x203A, 0x0000, 0x0000, 0x0000, 0x0000, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00F7, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x05B0, 0x05B1, 0x05B2, 0x05B3, 0x05B4, 0x05B5, 0x05B6, 0x05B7, + 0x05B8, 0x05B9, 0x0000, 0x05BB, 0x05BC, 0x05BD, 0x05BE, 0x05BF, + 0x05C0, 0x05C1, 0x05C2, 0x05C3, 0x05F0, 0x05F1, 0x05F2, 0x05F3, + 0x05F4, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, + 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, + 0x05E8, 0x05E9, 0x05EA, 0x0000, 0x0000, 0x200E, 0x200F, 0x0000 +}; + +#elif _CODE_PAGE == 1256 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1256(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x067E, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0679, 0x2039, 0x0152, 0x0686, 0x0698, 0x0688, + 0x06AF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x06A9, 0x2122, 0x0691, 0x203A, 0x0153, 0x200C, 0x200D, 0x06BA, + 0x00A0, 0x060C, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x06BE, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x061B, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x061F, + 0x06C1, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x00D7, + 0x0637, 0x0638, 0x0639, 0x063A, 0x0640, 0x0640, 0x0642, 0x0643, + 0x00E0, 0x0644, 0x00E2, 0x0645, 0x0646, 0x0647, 0x0648, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x0649, 0x064A, 0x00EE, 0x00EF, + 0x064B, 0x064C, 0x064D, 0x064E, 0x00F4, 0x064F, 0x0650, 0x00F7, + 0x0651, 0x00F9, 0x0652, 0x00FB, 0x00FC, 0x200E, 0x200F, 0x06D2 +} + +#elif _CODE_PAGE == 1257 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1257(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, + 0x0000, 0x2030, 0x0000, 0x2039, 0x0000, 0x00A8, 0x02C7, 0x00B8, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x0000, 0x2122, 0x0000, 0x203A, 0x0000, 0x00AF, 0x02DB, 0x0000, + 0x00A0, 0x0000, 0x00A2, 0x00A3, 0x00A4, 0x0000, 0x00A6, 0x00A7, + 0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x0157, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00E6, + 0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112, + 0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B, + 0x0160, 0x0143, 0x0145, 0x00D3, 0x014C, 0x00D5, 0x00D6, 0x00D7, + 0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF, + 0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113, + 0x010D, 0x00E9, 0x017A, 0x0117, 0x0123, 0x0137, 0x012B, 0x013C, + 0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7, + 0x0173, 0x014E, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x02D9 +}; + +#elif _CODE_PAGE == 1258 +#define _TBLDEF 1 +static +const WCHAR Tbl[] = { /* CP1258(0x80-0xFF) to Unicode conversion table */ + 0x20AC, 0x0000, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0000, 0x2039, 0x0152, 0x0000, 0x0000, 0x0000, + 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0000, 0x203A, 0x0153, 0x0000, 0x0000, 0x0178, + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x0300, 0x00CD, 0x00CE, 0x00CF, + 0x0110, 0x00D1, 0x0309, 0x00D3, 0x00D4, 0x01A0, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x01AF, 0x0303, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x0301, 0x00ED, 0x00EE, 0x00EF, + 0x0111, 0x00F1, 0x0323, 0x00F3, 0x00F4, 0x01A1, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x01B0, 0x20AB, 0x00FF +}; + +#endif + + +#if !_TBLDEF || !_USE_LFN +#error This file is not needed in current configuration. Remove from the project. +#endif + + +WCHAR ff_convert ( /* Converted character, Returns zero on error */ + WCHAR chr, /* Character code to be converted */ + UINT dir /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */ +) +{ + WCHAR c; + + + if (chr < 0x80) { /* ASCII */ + c = chr; + + } else { + if (dir) { /* OEMCP to Unicode */ + c = (chr >= 0x100) ? 0 : Tbl[chr - 0x80]; + + } else { /* Unicode to OEMCP */ + for (c = 0; c < 0x80; c++) { + if (chr == Tbl[c]) break; + } + c = (c + 0x80) & 0xFF; + } + } + + return c; +} + +#endif //WITH_FILESYSTEM + diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/diskio.cpp b/lib/miosix-kernel/miosix/filesystem/fat32/diskio.cpp new file mode 100644 index 00000000..33fd9e17 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/diskio.cpp @@ -0,0 +1,112 @@ +/* + * Integration of FatFs filesystem module in Miosix by Terraneo Federico + * based on original files diskio.c and mmc.c by ChaN + */ + +#include "diskio.h" +#include "filesystem/ioctl.h" +#include "config/miosix_settings.h" + +#ifdef WITH_FILESYSTEM + +using namespace miosix; + +// #ifdef __cplusplus +// extern "C" { +// #endif + +///** +// * \internal +// * Initializes drive. +// */ +//DSTATUS disk_initialize ( +// intrusive_ref_ptr pdrv /* Physical drive nmuber (0..) */ +//) +//{ +// if(Disk::isAvailable()==false) return STA_NODISK; +// Disk::init(); +// if(Disk::isInitialized()) return RES_OK; +// else return STA_NOINIT; +//} + +///** +// * \internal +// * Return status of drive. +// */ +//DSTATUS disk_status ( +// intrusive_ref_ptr pdrv /* Physical drive nmuber (0..) */ +//) +//{ +// if(Disk::isInitialized()) return RES_OK; +// else return STA_NOINIT; +//} + +/** + * \internal + * Read one or more sectors from drive + */ +DRESULT disk_read ( + intrusive_ref_ptr pdrv, /* Physical drive nmuber (0..) */ + BYTE *buff, /* Data buffer to store read data */ + DWORD sector, /* Sector address (LBA) */ + UINT count /* Number of sectors to read (1..255) */ +) +{ + if(pdrv->lseek(static_cast(sector)*512,SEEK_SET)<0) return RES_ERROR; + if(pdrv->read(buff,count*512)!=static_cast(count)*512) return RES_ERROR; + return RES_OK; +} + +/** + * \internal + * Write one or more sectors to drive + */ +DRESULT disk_write ( + intrusive_ref_ptr pdrv, /* Physical drive nmuber (0..) */ + const BYTE *buff, /* Data to be written */ + DWORD sector, /* Sector address (LBA) */ + UINT count /* Number of sectors to write (1..255) */ +) +{ + if(pdrv->lseek(static_cast(sector)*512,SEEK_SET)<0) return RES_ERROR; + if(pdrv->write(buff,count*512)!=static_cast(count)*512) return RES_ERROR; + return RES_OK; +} + +/** + * \internal + * To perform disk functions other thar read/write + */ +DRESULT disk_ioctl ( + intrusive_ref_ptr pdrv, /* Physical drive nmuber (0..) */ + BYTE ctrl, /* Control code */ + void *buff /* Buffer to send/receive control data */ +) +{ + switch(ctrl) + { + case CTRL_SYNC: + if(pdrv->ioctl(IOCTL_SYNC,0)==0) return RES_OK; else return RES_ERROR; + case GET_SECTOR_COUNT: + return RES_ERROR; //unimplemented, so f_mkfs() does not work + case GET_BLOCK_SIZE: + return RES_ERROR; //unimplemented, so f_mkfs() does not work + default: + return RES_PARERR; + } +} + +/** + * \internal + * Return current time, used to save file creation time + */ + DWORD get_fattime() + { + return 0x210000;//TODO: this stub just returns date 01/01/1980 0.00.00 + } + +// #ifdef __cplusplus +// } +// #endif + +#endif //WITH_FILESYSTEM diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/diskio.h b/lib/miosix-kernel/miosix/filesystem/fat32/diskio.h new file mode 100644 index 00000000..3eac1926 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/diskio.h @@ -0,0 +1,97 @@ +/*----------------------------------------------------------------------- +/ Low level disk interface modlue include file (C)ChaN, 2013 +/-----------------------------------------------------------------------*/ + +#ifndef _DISKIO_DEFINED +#define _DISKIO_DEFINED + +//#ifdef __cplusplus +//extern "C" { +//#endif + +#define _USE_WRITE 1 /* 1: Enable disk_write function */ +#define _USE_IOCTL 1 /* 1: Enable disk_ioctl fucntion */ + +#include "integer.h" +#include +#include "config/miosix_settings.h" + +#ifdef WITH_FILESYSTEM + + +/* Status of Disk Functions */ +typedef BYTE DSTATUS; + +/* Results of Disk Functions */ +typedef enum { + RES_OK = 0, /* 0: Successful */ + RES_ERROR, /* 1: R/W Error */ + RES_WRPRT, /* 2: Write Protected */ + RES_NOTRDY, /* 3: Not Ready */ + RES_PARERR /* 4: Invalid Parameter */ +} DRESULT; + + +/*---------------------------------------*/ +/* Prototypes for disk control functions */ + + +DSTATUS disk_initialize (miosix::intrusive_ref_ptr pdrv); +DSTATUS disk_status (miosix::intrusive_ref_ptr pdrv); +DRESULT disk_read (miosix::intrusive_ref_ptr pdrv, + BYTE*buff, DWORD sector, UINT count); +DRESULT disk_write (miosix::intrusive_ref_ptr pdrv, + const BYTE* buff, DWORD sector, UINT count); +DRESULT disk_ioctl (miosix::intrusive_ref_ptr pdrv, + BYTE cmd, void* buff); + + +/* Disk Status Bits (DSTATUS) */ +#define STA_NOINIT 0x01 /* Drive not initialized */ +#define STA_NODISK 0x02 /* No medium in the drive */ +#define STA_PROTECT 0x04 /* Write protected */ + + +/* Command code for disk_ioctrl fucntion */ + +/* Generic command (used by FatFs) */ +#define CTRL_SYNC 0 /* Flush disk cache (for write functions) */ +#define GET_SECTOR_COUNT 1 /* Get media size (for only f_mkfs()) */ +#define GET_SECTOR_SIZE 2 /* Get sector size (for multiple sector size (_MAX_SS >= 1024)) */ +#define GET_BLOCK_SIZE 3 /* Get erase block size (for only f_mkfs()) */ +#define CTRL_ERASE_SECTOR 4 /* Force erased a block of sectors (for only _USE_ERASE) */ + +/* Generic command (not used by FatFs) */ +#define CTRL_POWER 5 /* Get/Set power status */ +#define CTRL_LOCK 6 /* Lock/Unlock media removal */ +#define CTRL_EJECT 7 /* Eject media */ +#define CTRL_FORMAT 8 /* Create physical format on the media */ + +/* MMC/SDC specific ioctl command */ +#define MMC_GET_TYPE 10 /* Get card type */ +#define MMC_GET_CSD 11 /* Get CSD */ +#define MMC_GET_CID 12 /* Get CID */ +#define MMC_GET_OCR 13 /* Get OCR */ +#define MMC_GET_SDSTAT 14 /* Get SD status */ + +/* ATA/CF specific ioctl command */ +#define ATA_GET_REV 20 /* Get F/W revision */ +#define ATA_GET_MODEL 21 /* Get model name */ +#define ATA_GET_SN 22 /* Get serial number */ + + +/* MMC card type flags (MMC_GET_TYPE) */ +#define CT_MMC 0x01 /* MMC ver 3 */ +#define CT_SD1 0x02 /* SD ver 1 */ +#define CT_SD2 0x04 /* SD ver 2 */ +#define CT_SDC (CT_SD1|CT_SD2) /* SD */ +#define CT_BLOCK 0x08 /* Block addressing */ + +#endif //WITH_FILESYSTEM + + +//#ifdef __cplusplus +//} +//#endif + +#endif diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/fat32.cpp b/lib/miosix-kernel/miosix/filesystem/fat32/fat32.cpp new file mode 100644 index 00000000..f35699d5 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/fat32.cpp @@ -0,0 +1,510 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "fat32.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "filesystem/stringpart.h" +#include "filesystem/ioctl.h" +#include "util/unicode.h" + +using namespace std; + +namespace miosix { + +#ifdef WITH_FILESYSTEM + +/** + * Translate between FATFS error codes and POSIX ones + * \param ec FATS error code + * \return POSIX error code + */ +static int translateError(int ec) +{ + switch(ec) + { + case FR_OK: + return 0; + case FR_NO_FILE: + case FR_NO_PATH: + return -ENOENT; + case FR_DENIED: + return -ENOSPC; + case FR_EXIST: + return -EEXIST; + case FR_WRITE_PROTECTED: + return -EROFS; + case FR_LOCKED: + return -EBUSY; + case FR_NOT_ENOUGH_CORE: + return -ENOMEM; + case FR_TOO_MANY_OPEN_FILES: + return -ENFILE; + default: + return -EACCES; + } +} + +/** + * Directory class for Fat32Fs + */ +class Fat32Directory : public DirectoryBase +{ +public: + /** + * \param parent parent filesystem + * \param mutex mutex to lock when accessing the fiesystem + * \param currentInode inode value for '.' entry + * \param parentInode inode value for '..' entry + */ + Fat32Directory(intrusive_ref_ptr parent, FastMutex& mutex, + int currentInode, int parentInode) : DirectoryBase(parent), + mutex(mutex), currentInode(currentInode), parentInode(parentInode), + first(true), unfinished(false) + { + //Make sure a closedir of an uninitialized dir won't do any damage + dir.fs=0; + fi.lfname=lfn; + fi.lfsize=sizeof(lfn); + } + + /** + * \return the underlying directory object + */ + DIR_ *directory() { return &dir; } + + /** + * Also directories can be opened as files. In this case, this system call + * allows to retrieve directory entries. + * \param dp pointer to a memory buffer where one or more struct dirent + * will be placed. dp must be four words aligned. + * \param len memory buffer size. + * \return the number of bytes read on success, or a negative number on + * failure. + */ + virtual int getdents(void *dp, int len); + + /** + * Destructor + */ + virtual ~Fat32Directory(); + +private: + FastMutex& mutex; ///< Parent filesystem's mutex + DIR_ dir; ///< Directory object + FILINFO fi; ///< Information on a file + int currentInode; ///< Inode of '.' + int parentInode; ///< Inode of '..' + bool first; ///< To display '.' and '..' entries + bool unfinished; ///< True if fi contains unread data + char lfn[(_MAX_LFN+1)*2]; ///< Long file name +}; + +// +// class Fat32Directory +// + +int Fat32Directory::getdents(void *dp, int len) +{ + if(len(dp); + char *buffer=begin; + char *end=buffer+len; + + Lock l(mutex); + if(first) + { + first=false; + addDefaultEntries(&buffer,currentInode,parentInode); + } + if(unfinished) + { + unfinished=false; + char type=fi.fattrib & AM_DIR ? DT_DIR : DT_REG; + StringPart name(fi.lfname); + if(addEntry(&buffer,end,fi.inode,type,name)<0) return -EINVAL; + } + for(;;) + { + if(int res=translateError(f_readdir(&dir,&fi))) return res; + if(fi.fname[0]=='\0') + { + addTerminatingEntry(&buffer,end); + return buffer-begin; + } + char type=fi.fattrib & AM_DIR ? DT_DIR : DT_REG; + StringPart name(fi.lfname); + if(addEntry(&buffer,end,fi.inode,type,name)<0) + { + unfinished=true; + return buffer-begin; + } + } +} + +Fat32Directory::~Fat32Directory() +{ + Lock l(mutex); + f_closedir(&dir); +} + +/** + * Files of the Fat32Fs filesystem + */ +class Fat32File : public FileBase +{ +public: + /** + * Constructor + * \param parent the filesystem to which this file belongs + */ + Fat32File(intrusive_ref_ptr parent, FastMutex& mutex); + + /** + * Write data to the file, if the file supports writing. + * \param data the data to write + * \param len the number of bytes to write + * \return the number of written characters, or a negative number in case + * of errors + */ + virtual ssize_t write(const void *data, size_t len); + + /** + * Read data from the file, if the file supports reading. + * \param data buffer to store read data + * \param len the number of bytes to read + * \return the number of read characters, or a negative number in case + * of errors + */ + virtual ssize_t read(void *data, size_t len); + + /** + * Move file pointer, if the file supports random-access. + * \param pos offset to sum to the beginning of the file, current position + * or end of file, depending on whence + * \param whence SEEK_SET, SEEK_CUR or SEEK_END + * \return the offset from the beginning of the file if the operation + * completed, or a negative number in case of errors + */ + virtual off_t lseek(off_t pos, int whence); + + /** + * Return file information. + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + virtual int fstat(struct stat *pstat) const; + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + virtual int ioctl(int cmd, void *arg); + + /** + * \return the FatFs FIL object + */ + FIL *fil() { return &file; } + + /** + * \param inode file inode + */ + void setInode(int inode) { this->inode=inode; } + + /** + * Destructor + */ + ~Fat32File(); + +private: + FIL file; + FastMutex& mutex; + int inode; +}; + +// +// class Fat32File +// + +Fat32File::Fat32File(intrusive_ref_ptr parent, FastMutex& mutex) + : FileBase(parent), mutex(mutex), inode(0) {} + +ssize_t Fat32File::write(const void *data, size_t len) +{ + Lock l(mutex); + unsigned int bytesWritten; + if(int res=translateError(f_write(&file,data,len,&bytesWritten))) return res; + #ifdef SYNC_AFTER_WRITE + if(f_sync(&file)!=FR_OK) return -EIO; + #endif //SYNC_AFTER_WRITE + return static_cast(bytesWritten); +} + +ssize_t Fat32File::read(void *data, size_t len) +{ + Lock l(mutex); + unsigned int bytesRead; + if(int res=translateError(f_read(&file,data,len,&bytesRead))) return res; + return static_cast(bytesRead); +} + +off_t Fat32File::lseek(off_t pos, int whence) +{ + Lock l(mutex); + off_t offset; + switch(whence) + { + case SEEK_CUR: + offset=static_cast(f_tell(&file))+pos; + break; + case SEEK_SET: + offset=pos; + break; + case SEEK_END: + offset=static_cast(f_size(&file))+pos; + break; + default: + return -EINVAL; + } + //We don't support seek past EOF for Fat32 + if(offset<0 || offset>static_cast(f_size(&file))) return -EOVERFLOW; + if(int result=translateError( + f_lseek(&file,static_cast(offset)))) return result; + return offset; +} + +int Fat32File::fstat(struct stat *pstat) const +{ + memset(pstat,0,sizeof(struct stat)); + pstat->st_dev=getParent()->getFsId(); + pstat->st_ino=inode; + pstat->st_mode=S_IFREG | 0755; //-rwxr-xr-x + pstat->st_nlink=1; + pstat->st_size=f_size(&file); + pstat->st_blksize=512; + pstat->st_blocks=(static_cast(f_size(&file))+511)/512; + return 0; +} + +int Fat32File::ioctl(int cmd, void *arg) +{ + if(cmd!=IOCTL_SYNC) return -ENOTTY; + Lock l(mutex); + return translateError(f_sync(&file)); +} + +Fat32File::~Fat32File() +{ + Lock l(mutex); + if(inode) f_close(&file); //TODO: what to do with error code? +} + +// +// class Fat32Fs +// + +Fat32Fs::Fat32Fs(intrusive_ref_ptr disk) + : mutex(FastMutex::RECURSIVE), failed(true) +{ + filesystem.drv=disk; + failed=f_mount(&filesystem,1,false)!=FR_OK; +} + +int Fat32Fs::open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode) +{ + if(failed) return -ENOENT; + flags++; //To convert from O_RDONLY, O_WRONLY, ... to _FREAD, _FWRITE, ... + + // Code path checklist: + // Not existent | Regular file | Directory | + // ok | ok | ok | _FREAD + // ok | ok | ok | _FWRITE + // ok | ok | ok | _FWRITE | _FCREAT + + struct stat st; + bool statFailed=false; + if(int result=lstat(name,&st)) + { + //If _FCREAT the file may not yet exist as we are asked to create it + if((flags & (_FWRITE | _FCREAT)) != (_FWRITE | _FCREAT)) return result; + else statFailed=true; + } + + //Using if short circuit, as st is not initialized if statFailed + if(statFailed || !S_ISDIR(st.st_mode)) + { + //About to open a file + BYTE openflags=0; + if(flags & _FREAD) openflags|=FA_READ; + if(flags & _FWRITE) openflags|=FA_WRITE; + if(flags & _FTRUNC) openflags|=FA_CREATE_ALWAYS;//Truncate + else if(flags & _FCREAT) openflags|=FA_OPEN_ALWAYS;//If !exists create + else openflags|=FA_OPEN_EXISTING;//If not exists fail + + intrusive_ref_ptr f(new Fat32File(shared_from_this(),mutex)); + Lock l(mutex); + if(int res=translateError(f_open(&filesystem,f->fil(),name.c_str(),openflags))) + return res; + if(statFailed) + { + //If we didn't stat before, stat now to get the inode + if(int result=lstat(name,&st)) return result; + } + f->setInode(st.st_ino); + + //Can't open files larger than INT_MAX + if(static_cast(f_size(f->fil()))<0) return -EOVERFLOW; + + #ifdef SYNC_AFTER_WRITE + if(f_sync(f->fil())!=FR_OK) return -EFAULT; + #endif //SYNC_AFTER_WRITE + + //If file opened for appending, seek to end of file + if(flags & _FAPPEND) + if(f_lseek(f->fil(),f_size(f->fil()))!=FR_OK) return -EFAULT; + + file=f; + return 0; + } else { + //About to open a directory + if(flags & (_FWRITE | _FAPPEND | _FCREAT | _FTRUNC)) return -EISDIR; + + int parentInode; + if(name.empty()==false) + { + unsigned int lastSlash=name.findLastOf('/'); + if(lastSlash!=string::npos) + { + StringPart parent(name,lastSlash); + struct stat st2; + if(int result=lstat(parent,&st2)) return result; + parentInode=st2.st_ino; + } else parentInode=1; //Asked to list subdir of root + } else parentInode=parentFsMountpointInode; //Asked to list root dir + + + intrusive_ref_ptr d( + new Fat32Directory(shared_from_this(),mutex,st.st_ino,parentInode)); + + Lock l(mutex); + if(int res=translateError(f_opendir(&filesystem,d->directory(),name.c_str()))) + return res; + + file=d; + return 0; + } +} + +int Fat32Fs::lstat(StringPart& name, struct stat *pstat) +{ + if(failed) return -ENOENT; + memset(pstat,0,sizeof(struct stat)); + pstat->st_dev=filesystemId; + pstat->st_nlink=1; + pstat->st_blksize=512; + + Lock l(mutex); + if(name.empty()) + { + //We are asked to stat the filesystem's root directory + //By convention, we use 1 for root dir inode, see INODE() macro in ff.c + pstat->st_ino=1; + pstat->st_mode=S_IFDIR | 0755; //drwxr-xr-x + return 0; + } + FILINFO info; + info.lfname=0; //We're not interested in getting the lfname + info.lfsize=0; + if(int result=translateError(f_stat(&filesystem,name.c_str(),&info))) return result; + + pstat->st_ino=info.inode; + pstat->st_mode=(info.fattrib & AM_DIR) ? + S_IFDIR | 0755 //drwxr-xr-x + : S_IFREG | 0755; //-rwxr-xr-x + pstat->st_size=info.fsize; + pstat->st_blocks=(info.fsize+511)/512; + return 0; +} + +int Fat32Fs::unlink(StringPart& name) +{ + return unlinkRmdirHelper(name,false); +} + +int Fat32Fs::rename(StringPart& oldName, StringPart& newName) +{ + if(failed) return -ENOENT; + Lock l(mutex); + return translateError(f_rename(&filesystem,oldName.c_str(),newName.c_str())); +} + +int Fat32Fs::mkdir(StringPart& name, int mode) +{ + if(failed) return -ENOENT; + Lock l(mutex); + return translateError(f_mkdir(&filesystem,name.c_str())); +} + +int Fat32Fs::rmdir(StringPart& name) +{ + return unlinkRmdirHelper(name,true); +} + +Fat32Fs::~Fat32Fs() +{ + if(failed) return; + f_mount(&filesystem,0,true); //TODO: what to do with error code? + filesystem.drv->ioctl(IOCTL_SYNC,0); + filesystem.drv.reset(); +} + +int Fat32Fs::unlinkRmdirHelper(StringPart& name, bool delDir) +{ + if(failed) return -ENOENT; + Lock l(mutex); + struct stat st; + if(int result=lstat(name,&st)) return result; + if(delDir) + { + if(!S_ISDIR(st.st_mode)) return -ENOTDIR; + } else if(S_ISDIR(st.st_mode)) return -EISDIR; + return translateError(f_unlink(&filesystem,name.c_str())); +} + +#endif //WITH_FILESYSTEM + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/fat32.h b/lib/miosix-kernel/miosix/filesystem/fat32/fat32.h new file mode 100644 index 00000000..29c29e7b --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/fat32.h @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef FAT32_H +#define FAT32_H + +#include "filesystem/file.h" +#include "kernel/sync.h" +#include "ff.h" +#include "config/miosix_settings.h" + +namespace miosix { + +#ifdef WITH_FILESYSTEM + +/** + * Fat32 Filesystem. + */ +class Fat32Fs : public FilesystemBase +{ +public: + /** + * Constructor + */ + Fat32Fs(intrusive_ref_ptr disk); + + /** + * Open a file + * \param file the file object will be stored here, if the call succeeds + * \param name the name of the file to open, relative to the local + * filesystem + * \param flags file flags (open for reading, writing, ...) + * \param mode file permissions + * \return 0 on success, or a negative number on failure + */ + virtual int open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode); + + /** + * Obtain information on a file, identified by a path name. Does not follow + * symlinks + * \param name path name, relative to the local filesystem + * \param pstat file information is stored here + * \return 0 on success, or a negative number on failure + */ + virtual int lstat(StringPart& name, struct stat *pstat); + + /** + * Remove a file or directory + * \param name path name of file or directory to remove + * \return 0 on success, or a negative number on failure + */ + virtual int unlink(StringPart& name); + + /** + * Rename a file or directory + * \param oldName old file name + * \param newName new file name + * \return 0 on success, or a negative number on failure + */ + virtual int rename(StringPart& oldName, StringPart& newName); + + /** + * Create a directory + * \param name directory name + * \param mode directory permissions + * \return 0 on success, or a negative number on failure + */ + virtual int mkdir(StringPart& name, int mode); + + /** + * Remove a directory if empty + * \param name directory name + * \return 0 on success, or a negative number on failure + */ + virtual int rmdir(StringPart& name); + + /** + * \return true if the filesystem failed to mount + */ + bool mountFailed() const { return failed; } + + /** + * Destructor + */ + ~Fat32Fs(); + +private: + + int unlinkRmdirHelper(StringPart& name, bool delDir); + + FATFS filesystem; + FastMutex mutex; + bool failed; ///< Failed to mount +}; + +#endif //WITH_FILESYSTEM + +} //namespace miosix + +#endif //FAT32_H diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/ff.cpp b/lib/miosix-kernel/miosix/filesystem/fat32/ff.cpp new file mode 100644 index 00000000..845b8599 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/ff.cpp @@ -0,0 +1,4617 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - FAT file system module R0.10 (C)ChaN, 2013 +/-----------------------------------------------------------------------------/ +/ FatFs module is a generic FAT file system module for small embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2013, ChaN, all right reserved. +/ +/ * The FatFs module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/-----------------------------------------------------------------------------/ +/ Feb 26,'06 R0.00 Prototype. +/ +/ Apr 29,'06 R0.01 First stable version. +/ +/ Jun 01,'06 R0.02 Added FAT12 support. +/ Removed unbuffered mode. +/ Fixed a problem on small (<32M) partition. +/ Jun 10,'06 R0.02a Added a configuration option (_FS_MINIMUM). +/ +/ Sep 22,'06 R0.03 Added f_rename(). +/ Changed option _FS_MINIMUM to _FS_MINIMIZE. +/ Dec 11,'06 R0.03a Improved cluster scan algorithm to write files fast. +/ Fixed f_mkdir() creates incorrect directory on FAT32. +/ +/ Feb 04,'07 R0.04 Supported multiple drive system. +/ Changed some interfaces for multiple drive system. +/ Changed f_mountdrv() to f_mount(). +/ Added f_mkfs(). +/ Apr 01,'07 R0.04a Supported multiple partitions on a physical drive. +/ Added a capability of extending file size to f_lseek(). +/ Added minimization level 3. +/ Fixed an endian sensitive code in f_mkfs(). +/ May 05,'07 R0.04b Added a configuration option _USE_NTFLAG. +/ Added FSINFO support. +/ Fixed DBCS name can result FR_INVALID_NAME. +/ Fixed short seek (<= csize) collapses the file object. +/ +/ Aug 25,'07 R0.05 Changed arguments of f_read(), f_write() and f_mkfs(). +/ Fixed f_mkfs() on FAT32 creates incorrect FSINFO. +/ Fixed f_mkdir() on FAT32 creates incorrect directory. +/ Feb 03,'08 R0.05a Added f_truncate() and f_utime(). +/ Fixed off by one error at FAT sub-type determination. +/ Fixed btr in f_read() can be mistruncated. +/ Fixed cached sector is not flushed when create and close without write. +/ +/ Apr 01,'08 R0.06 Added fputc(), fputs(), fprintf() and fgets(). +/ Improved performance of f_lseek() on moving to the same or following cluster. +/ +/ Apr 01,'09 R0.07 Merged Tiny-FatFs as a configuration option. (_FS_TINY) +/ Added long file name feature. +/ Added multiple code page feature. +/ Added re-entrancy for multitask operation. +/ Added auto cluster size selection to f_mkfs(). +/ Added rewind option to f_readdir(). +/ Changed result code of critical errors. +/ Renamed string functions to avoid name collision. +/ Apr 14,'09 R0.07a Separated out OS dependent code on reentrant cfg. +/ Added multiple sector size feature. +/ Jun 21,'09 R0.07c Fixed f_unlink() can return FR_OK on error. +/ Fixed wrong cache control in f_lseek(). +/ Added relative path feature. +/ Added f_chdir() and f_chdrive(). +/ Added proper case conversion to extended character. +/ Nov 03,'09 R0.07e Separated out configuration options from ff.h to ffconf.h. +/ Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH. +/ Fixed name matching error on the 13 character boundary. +/ Added a configuration option, _LFN_UNICODE. +/ Changed f_readdir() to return the SFN with always upper case on non-LFN cfg. +/ +/ May 15,'10 R0.08 Added a memory configuration option. (_USE_LFN = 3) +/ Added file lock feature. (_FS_SHARE) +/ Added fast seek feature. (_USE_FASTSEEK) +/ Changed some types on the API, XCHAR->TCHAR. +/ Changed .fname in the FILINFO structure on Unicode cfg. +/ String functions support UTF-8 encoding files on Unicode cfg. +/ Aug 16,'10 R0.08a Added f_getcwd(). +/ Added sector erase feature. (_USE_ERASE) +/ Moved file lock semaphore table from fs object to the bss. +/ Fixed a wrong directory entry is created on non-LFN cfg when the given name contains ';'. +/ Fixed f_mkfs() creates wrong FAT32 volume. +/ Jan 15,'11 R0.08b Fast seek feature is also applied to f_read() and f_write(). +/ f_lseek() reports required table size on creating CLMP. +/ Extended format syntax of f_printf(). +/ Ignores duplicated directory separators in given path name. +/ +/ Sep 06,'11 R0.09 f_mkfs() supports multiple partition to complete the multiple partition feature. +/ Added f_fdisk(). +/ Aug 27,'12 R0.09a Changed f_open() and f_opendir() reject null object pointer to avoid crash. +/ Changed option name _FS_SHARE to _FS_LOCK. +/ Fixed assertion failure due to OS/2 EA on FAT12/16 volume. +/ Jan 24,'13 R0.09b Added f_setlabel() and f_getlabel(). +/ +/ Oct 02,'13 R0.10 Added selection of character encoding on the file. (_STRF_ENCODE) +/ Added f_closedir(). +/ Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO) +/ Added forced mount feature with changes of f_mount(). +/ Improved behavior of volume auto detection. +/ Improved write throughput of f_puts() and f_printf(). +/ Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write(). +/ Fixed f_write() can be truncated when the file size is close to 4GB. +/ Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect error code. +/---------------------------------------------------------------------------*/ + +#include "ff.h" /* Declarations of FatFs API */ +#include "diskio.h" /* Declarations of disk I/O functions */ + +// Added by TFT -- begin +#include +#include +#include +#include +#include "config/miosix_settings.h" + +#ifdef WITH_FILESYSTEM + +/** + * FAT32 does not have the concept of inodes, but we need them. + * This code thus uses the sector # containing the directory entry and the + * index within the sector where the directory entry is located as inode. + * This code has the following limitations: + * - If _FS_RPATH is defined and INODE() is applied to the '.' and '..' + * entries, it returns inconsistent results. That's because for example the + * inode of '..' must be the same of the '.' inode of the parent directory, + * but these are in two different directories, so the sector # are different. + * This has been fixed by disabling _FS_RPATH and filling those entries + * manually. + * - It assumes that one sector is 512 byte, so that 16 directory entries fit + * in one sector. + * - If there are more than 2^32/16 sectors (filesystems > 128GByte) inodes + * are no longer unique! + * This code also guarantees not to ever return inode values of 0 and 1, as + * zero is an invalid inode, and 1 is reserved by the Fat32Fs code for the + * '.' entry of the root directory of the filesystem. If the algorithm for + * some reason, such as a >128GB filesystem would return 0 or 1, it always + * returns 2. + */ +#define INODE(x) ((x->sect<<4 | x->index % 16)<3) ? 2 : (x->sect<<4 | x->index % 16) +// Added by TFT -- end + + +/*-------------------------------------------------------------------------- + + Module Private Definitions + +---------------------------------------------------------------------------*/ + +#if _FATFS != 80960 /* Revision ID */ +#error Wrong include file (ff.h). +#endif + + +/* Definitions on sector size */ +#if _MAX_SS != 512 && _MAX_SS != 1024 && _MAX_SS != 2048 && _MAX_SS != 4096 +#error Wrong sector size. +#endif +#if _MAX_SS != 512 +#define SS(fs) ((fs)->ssize) /* Variable sector size */ +#else +#define SS(fs) 512U /* Fixed sector size */ +#endif + + +/* Reentrancy related */ +#if _FS_REENTRANT +#if _USE_LFN == 1 +#error Static LFN work area cannot be used at thread-safe configuration. +#endif +#define ENTER_FF(fs) { if (!lock_fs(fs)) return FR_TIMEOUT; } +#define LEAVE_FF(fs, res) { unlock_fs(fs, res); return res; } +#else +#define ENTER_FF(fs) +#define LEAVE_FF(fs, res) return res +#endif + +#define ABORT(fs, res) { fp->err = (BYTE)(res); LEAVE_FF(fs, res); } + + + + + + +/* DBCS code ranges and SBCS extend character conversion table */ + +#if _CODE_PAGE == 932 /* Japanese Shift-JIS */ +#define _DF1S 0x81 /* DBC 1st byte range 1 start */ +#define _DF1E 0x9F /* DBC 1st byte range 1 end */ +#define _DF2S 0xE0 /* DBC 1st byte range 2 start */ +#define _DF2E 0xFC /* DBC 1st byte range 2 end */ +#define _DS1S 0x40 /* DBC 2nd byte range 1 start */ +#define _DS1E 0x7E /* DBC 2nd byte range 1 end */ +#define _DS2S 0x80 /* DBC 2nd byte range 2 start */ +#define _DS2E 0xFC /* DBC 2nd byte range 2 end */ + +#elif _CODE_PAGE == 936 /* Simplified Chinese GBK */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0x80 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 949 /* Korean */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x41 +#define _DS1E 0x5A +#define _DS2S 0x61 +#define _DS2E 0x7A +#define _DS3S 0x81 +#define _DS3E 0xFE + +#elif _CODE_PAGE == 950 /* Traditional Chinese Big5 */ +#define _DF1S 0x81 +#define _DF1E 0xFE +#define _DS1S 0x40 +#define _DS1E 0x7E +#define _DS2S 0xA1 +#define _DS2E 0xFE + +#elif _CODE_PAGE == 437 /* U.S. (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0x41,0x8E,0x41,0x8F,0x80,0x45,0x45,0x45,0x49,0x49,0x49,0x8E,0x8F,0x90,0x92,0x92,0x4F,0x99,0x4F,0x55,0x55,0x59,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 720 /* Arabic (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x45,0x41,0x84,0x41,0x86,0x43,0x45,0x45,0x45,0x49,0x49,0x8D,0x8E,0x8F,0x90,0x92,0x92,0x93,0x94,0x95,0x49,0x49,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 737 /* Greek (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x92,0x92,0x93,0x94,0x95,0x96,0x97,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87, \ + 0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0xAA,0x92,0x93,0x94,0x95,0x96,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x97,0xEA,0xEB,0xEC,0xE4,0xED,0xEE,0xE7,0xE8,0xF1,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 775 /* Baltic (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x91,0xA0,0x8E,0x95,0x8F,0x80,0xAD,0xED,0x8A,0x8A,0xA1,0x8D,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0x95,0x96,0x97,0x97,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xE0,0xA3,0xA3,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xB5,0xB6,0xB7,0xB8,0xBD,0xBE,0xC6,0xC7,0xA5,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE3,0xE8,0xE8,0xEA,0xEA,0xEE,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 850 /* Multilingual Latin 1 (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0xDE,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x59,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE7,0xE9,0xEA,0xEB,0xED,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 852 /* Latin 2 (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xDE,0x8F,0x80,0x9D,0xD3,0x8A,0x8A,0xD7,0x8D,0x8E,0x8F,0x90,0x91,0x91,0xE2,0x99,0x95,0x95,0x97,0x97,0x99,0x9A,0x9B,0x9B,0x9D,0x9E,0x9F, \ + 0xB5,0xD6,0xE0,0xE9,0xA4,0xA4,0xA6,0xA6,0xA8,0xA8,0xAA,0x8D,0xAC,0xB8,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBD,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC6,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD1,0xD1,0xD2,0xD3,0xD2,0xD5,0xD6,0xD7,0xB7,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE3,0xD5,0xE6,0xE6,0xE8,0xE9,0xE8,0xEB,0xED,0xED,0xDD,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xEB,0xFC,0xFC,0xFE,0xFF} + +#elif _CODE_PAGE == 855 /* Cyrillic (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x81,0x81,0x83,0x83,0x85,0x85,0x87,0x87,0x89,0x89,0x8B,0x8B,0x8D,0x8D,0x8F,0x8F,0x91,0x91,0x93,0x93,0x95,0x95,0x97,0x97,0x99,0x99,0x9B,0x9B,0x9D,0x9D,0x9F,0x9F, \ + 0xA1,0xA1,0xA3,0xA3,0xA5,0xA5,0xA7,0xA7,0xA9,0xA9,0xAB,0xAB,0xAD,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB6,0xB6,0xB8,0xB8,0xB9,0xBA,0xBB,0xBC,0xBE,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD1,0xD1,0xD3,0xD3,0xD5,0xD5,0xD7,0xD7,0xDD,0xD9,0xDA,0xDB,0xDC,0xDD,0xE0,0xDF, \ + 0xE0,0xE2,0xE2,0xE4,0xE4,0xE6,0xE6,0xE8,0xE8,0xEA,0xEA,0xEC,0xEC,0xEE,0xEE,0xEF,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF8,0xFA,0xFA,0xFC,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 857 /* Turkish (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0x98,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x98,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9E, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA6,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xDE,0x59,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 858 /* Multilingual Latin 1 + Euro (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x9A,0x90,0xB6,0x8E,0xB7,0x8F,0x80,0xD2,0xD3,0xD4,0xD8,0xD7,0xDE,0x8E,0x8F,0x90,0x92,0x92,0xE2,0x99,0xE3,0xEA,0xEB,0x59,0x99,0x9A,0x9D,0x9C,0x9D,0x9E,0x9F, \ + 0xB5,0xD6,0xE0,0xE9,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC7,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD1,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE5,0xE5,0xE6,0xE7,0xE7,0xE9,0xEA,0xEB,0xED,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 862 /* Hebrew (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x41,0x49,0x4F,0x55,0xA5,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0x21,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 866 /* Russian (OEM) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x90,0x91,0x92,0x93,0x9d,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xF0,0xF0,0xF2,0xF2,0xF4,0xF4,0xF6,0xF6,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 874 /* Thai (OEM, Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 1250 /* Central Europe (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x8A,0x9B,0x8C,0x8D,0x8E,0x8F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xA3,0xB4,0xB5,0xB6,0xB7,0xB8,0xA5,0xAA,0xBB,0xBC,0xBD,0xBC,0xAF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF} + +#elif _CODE_PAGE == 1251 /* Cyrillic (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x82,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x80,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x8A,0x9B,0x8C,0x8D,0x8E,0x8F, \ + 0xA0,0xA2,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB2,0xA5,0xB5,0xB6,0xB7,0xA8,0xB9,0xAA,0xBB,0xA3,0xBD,0xBD,0xAF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF} + +#elif _CODE_PAGE == 1252 /* Latin 1 (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0xAd,0x9B,0x8C,0x9D,0xAE,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0x9F} + +#elif _CODE_PAGE == 1253 /* Greek (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xA2,0xB8,0xB9,0xBA, \ + 0xE0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xF2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xFB,0xBC,0xFD,0xBF,0xFF} + +#elif _CODE_PAGE == 1254 /* Turkish (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x8A,0x9B,0x8C,0x9D,0x9E,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0x9F} + +#elif _CODE_PAGE == 1255 /* Hebrew (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 1256 /* Arabic (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x8C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0x41,0xE1,0x41,0xE3,0xE4,0xE5,0xE6,0x43,0x45,0x45,0x45,0x45,0xEC,0xED,0x49,0x49,0xF0,0xF1,0xF2,0xF3,0x4F,0xF5,0xF6,0xF7,0xF8,0x55,0xFA,0x55,0x55,0xFD,0xFE,0xFF} + +#elif _CODE_PAGE == 1257 /* Baltic (Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, \ + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xA8,0xB9,0xAA,0xBB,0xBC,0xBD,0xBE,0xAF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF} + +#elif _CODE_PAGE == 1258 /* Vietnam (OEM, Windows) */ +#define _DF1S 0 +#define _EXCVT {0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0xAC,0x9D,0x9E,0x9F, \ + 0xA0,0x21,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, \ + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xEC,0xCD,0xCE,0xCF,0xD0,0xD1,0xF2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xFE,0x9F} + +#elif _CODE_PAGE == 1 /* ASCII (for only non-LFN cfg) */ +#if _USE_LFN +#error Cannot use LFN feature without valid code page. +#endif +#define _DF1S 0 + +#else +#error Unknown code page + +#endif + + +/* Character code support macros */ +#define IsUpper(c) (((c)>='A')&&((c)<='Z')) +#define IsLower(c) (((c)>='a')&&((c)<='z')) +#define IsDigit(c) (((c)>='0')&&((c)<='9')) + +#if _DF1S /* Code page is DBCS */ + +#ifdef _DF2S /* Two 1st byte areas */ +#define IsDBCS1(c) (((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) || ((BYTE)(c) >= _DF2S && (BYTE)(c) <= _DF2E)) +#else /* One 1st byte area */ +#define IsDBCS1(c) ((BYTE)(c) >= _DF1S && (BYTE)(c) <= _DF1E) +#endif + +#ifdef _DS3S /* Three 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E) || ((BYTE)(c) >= _DS3S && (BYTE)(c) <= _DS3E)) +#else /* Two 2nd byte areas */ +#define IsDBCS2(c) (((BYTE)(c) >= _DS1S && (BYTE)(c) <= _DS1E) || ((BYTE)(c) >= _DS2S && (BYTE)(c) <= _DS2E)) +#endif + +#else /* Code page is SBCS */ + +#define IsDBCS1(c) 0 +#define IsDBCS2(c) 0 + +#endif /* _DF1S */ + + +/* Name status flags */ +#define NS 11 /* Index of name status byte in fn[] */ +#define NS_LOSS 0x01 /* Out of 8.3 format */ +#define NS_LFN 0x02 /* Force to create LFN entry */ +#define NS_LAST 0x04 /* Last segment */ +#define NS_BODY 0x08 /* Lower case flag (body) */ +#define NS_EXT 0x10 /* Lower case flag (ext) */ +#define NS_DOT 0x20 /* Dot entry */ + + +/* FAT sub-type boundaries */ +#define MIN_FAT16 4086U /* Minimum number of clusters for FAT16 */ +#define MIN_FAT32 65526U /* Minimum number of clusters for FAT32 */ + + +/* FatFs refers the members in the FAT structures as byte array instead of +/ structure member because the structure is not binary compatible between +/ different platforms */ + +#define BS_jmpBoot 0 /* Jump instruction (3) */ +#define BS_OEMName 3 /* OEM name (8) */ +#define BPB_BytsPerSec 11 /* Sector size [byte] (2) */ +#define BPB_SecPerClus 13 /* Cluster size [sector] (1) */ +#define BPB_RsvdSecCnt 14 /* Size of reserved area [sector] (2) */ +#define BPB_NumFATs 16 /* Number of FAT copies (1) */ +#define BPB_RootEntCnt 17 /* Number of root directory entries for FAT12/16 (2) */ +#define BPB_TotSec16 19 /* Volume size [sector] (2) */ +#define BPB_Media 21 /* Media descriptor (1) */ +#define BPB_FATSz16 22 /* FAT size [sector] (2) */ +#define BPB_SecPerTrk 24 /* Track size [sector] (2) */ +#define BPB_NumHeads 26 /* Number of heads (2) */ +#define BPB_HiddSec 28 /* Number of special hidden sectors (4) */ +#define BPB_TotSec32 32 /* Volume size [sector] (4) */ +#define BS_DrvNum 36 /* Physical drive number (2) */ +#define BS_BootSig 38 /* Extended boot signature (1) */ +#define BS_VolID 39 /* Volume serial number (4) */ +#define BS_VolLab 43 /* Volume label (8) */ +#define BS_FilSysType 54 /* File system type (1) */ +#define BPB_FATSz32 36 /* FAT size [sector] (4) */ +#define BPB_ExtFlags 40 /* Extended flags (2) */ +#define BPB_FSVer 42 /* File system version (2) */ +#define BPB_RootClus 44 /* Root directory first cluster (4) */ +#define BPB_FSInfo 48 /* Offset of FSINFO sector (2) */ +#define BPB_BkBootSec 50 /* Offset of backup boot sector (2) */ +#define BS_DrvNum32 64 /* Physical drive number (2) */ +#define BS_BootSig32 66 /* Extended boot signature (1) */ +#define BS_VolID32 67 /* Volume serial number (4) */ +#define BS_VolLab32 71 /* Volume label (8) */ +#define BS_FilSysType32 82 /* File system type (1) */ +#define FSI_LeadSig 0 /* FSI: Leading signature (4) */ +#define FSI_StrucSig 484 /* FSI: Structure signature (4) */ +#define FSI_Free_Count 488 /* FSI: Number of free clusters (4) */ +#define FSI_Nxt_Free 492 /* FSI: Last allocated cluster (4) */ +#define MBR_Table 446 /* MBR: Partition table offset (2) */ +#define SZ_PTE 16 /* MBR: Size of a partition table entry */ +#define BS_55AA 510 /* Boot sector signature (2) */ + +#define DIR_Name 0 /* Short file name (11) */ +#define DIR_Attr 11 /* Attribute (1) */ +#define DIR_NTres 12 /* NT flag (1) */ +#define DIR_CrtTimeTenth 13 /* Created time sub-second (1) */ +#define DIR_CrtTime 14 /* Created time (2) */ +#define DIR_CrtDate 16 /* Created date (2) */ +#define DIR_LstAccDate 18 /* Last accessed date (2) */ +#define DIR_FstClusHI 20 /* Higher 16-bit of first cluster (2) */ +#define DIR_WrtTime 22 /* Modified time (2) */ +#define DIR_WrtDate 24 /* Modified date (2) */ +#define DIR_FstClusLO 26 /* Lower 16-bit of first cluster (2) */ +#define DIR_FileSize 28 /* File size (4) */ +#define LDIR_Ord 0 /* LFN entry order and LLE flag (1) */ +#define LDIR_Attr 11 /* LFN attribute (1) */ +#define LDIR_Type 12 /* LFN type (1) */ +#define LDIR_Chksum 13 /* Sum of corresponding SFN entry */ +#define LDIR_FstClusLO 26 /* Filled by zero (0) */ +#define SZ_DIR 32 /* Size of a directory entry */ +#define LLE 0x40 /* Last long entry flag in LDIR_Ord */ +#define DDE 0xE5 /* Deleted directory entry mark in DIR_Name[0] */ +#define NDDE 0x05 /* Replacement of the character collides with DDE */ + + +/*------------------------------------------------------------*/ +/* Module private work area */ +/*------------------------------------------------------------*/ +/* Note that uninitialized variables with static duration are +/ zeroed/nulled at start-up. If not, the compiler or start-up +/ routine is out of ANSI-C standard. +*/ + +#if _VOLUMES +//static +//FATFS *FatFs[_VOLUMES]; /* Pointer to the file system objects (logical drives) */ +#else +#error Number of volumes must not be 0. +#endif + +static +/*WORD*/ int Fsid; /* File system mount ID */ + +#if _FS_RPATH && _VOLUMES >= 2 +static +BYTE CurrVol; /* Current drive */ +#endif + +#if _FS_LOCK +//static +//FILESEM Files[_FS_LOCK]; /* Open object lock semaphores */ +#endif + +#if _USE_LFN == 0 /* No LFN feature */ +#define DEF_NAMEBUF BYTE sfn[12] +#define INIT_BUF(dobj) (dobj).fn = sfn +#define INIT_BUF2(dobj,fs) (dobj).fn = sfn +#define FREE_BUF() + +#elif _USE_LFN == 1 /* LFN feature with static working buffer */ +//static +//WCHAR LfnBuf[_MAX_LFN+1]; +#define DEF_NAMEBUF BYTE sfn[12] +#define INIT_BUF(dobj) { (dobj).fn = sfn; (dobj).lfn = fs->LfnBuf; } +#define INIT_BUF2(dobj,fs) { (dobj).fn = sfn; (dobj).lfn = fs->LfnBuf; } +#define FREE_BUF() + +#elif _USE_LFN == 2 /* LFN feature with dynamic working buffer on the stack */ +#define DEF_NAMEBUF BYTE sfn[12]; WCHAR lbuf[_MAX_LFN+1] +#define INIT_BUF(dobj) { (dobj).fn = sfn; (dobj).lfn = lbuf; } +#define FREE_BUF() + +#elif _USE_LFN == 3 /* LFN feature with dynamic working buffer on the heap */ +#define DEF_NAMEBUF BYTE sfn[12]; WCHAR *lfn +#define INIT_BUF(dobj) { lfn = ff_memalloc((_MAX_LFN + 1) * 2); \ + if (!lfn) LEAVE_FF((dobj).fs, FR_NOT_ENOUGH_CORE); \ + (dobj).lfn = lfn; (dobj).fn = sfn; } +#define FREE_BUF() ff_memfree(lfn) + +#else +#error Wrong LFN configuration. +#endif + + +#ifdef _EXCVT +static +const BYTE ExCvt[] = _EXCVT; /* Upper conversion table for extended characters */ +#endif + + + + + + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + + +/*-----------------------------------------------------------------------*/ +/* String functions */ +/*-----------------------------------------------------------------------*/ + +// Added by TFT -- begin + +//Using newlib's version of memcpy, memset, memcmp and strchr which are +//performance optimized, while these are size optimized ones. +#define mem_cpy memcpy +#define mem_set memset +#define mem_cmp memcmp + +/* Copy memory to memory */ +// static +// void mem_cpy (void* dst, const void* src, UINT cnt) { +// BYTE *d = (BYTE*)dst; +// const BYTE *s = (const BYTE*)src; +// +// #if _WORD_ACCESS == 1 +// while (cnt >= sizeof (int)) { +// *(int*)d = *(int*)s; +// d += sizeof (int); s += sizeof (int); +// cnt -= sizeof (int); +// } +// #endif +// while (cnt--) +// *d++ = *s++; +//} + +/* Fill memory */ +// static +// void mem_set (void* dst, int val, UINT cnt) { +// BYTE *d = (BYTE*)dst; +// +// while (cnt--) +// *d++ = (BYTE)val; +// } + +/* Compare memory to memory */ +// static +// int mem_cmp (const void* dst, const void* src, UINT cnt) { +// const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src; +// int r = 0; +// +// while (cnt-- && (r = *d++ - *s++) == 0) ; +// return r; +// } + +/* Check if chr is contained in the string */ +static +int chk_chr (const char* str, int chr) { + //while (*str && *str != chr) str++; + //return *str; + char *result=strchr(str,chr); + if(result) return *result; + else return 0; +} + +// Added by TFT -- end + + +/*-----------------------------------------------------------------------*/ +/* Request/Release grant to access the volume */ +/*-----------------------------------------------------------------------*/ +#if _FS_REENTRANT +static +int lock_fs ( + FATFS* fs /* File system object */ +) +{ + return ff_req_grant(fs->sobj); +} + + +static +void unlock_fs ( + FATFS* fs, /* File system object */ + FRESULT res /* Result code to be returned */ +) +{ + if (fs && + res != FR_NOT_ENABLED && + res != FR_INVALID_DRIVE && + res != FR_INVALID_OBJECT && + res != FR_TIMEOUT) { + ff_rel_grant(fs->sobj); + } +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* File lock control functions */ +/*-----------------------------------------------------------------------*/ +#if _FS_LOCK + +static +FRESULT chk_lock ( /* Check if the file can be accessed */ + DIR_* dp, /* Directory object pointing the file to be checked */ + int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i, be; + + /* Search file semaphore table */ + for (i = be = 0; i < _FS_LOCK; i++) { + if (dp->fs->Files[i].fs) { /* Existing entry */ + if (dp->fs->Files[i].fs == dp->fs && /* Check if the object matched with an open object */ + dp->fs->Files[i].clu == dp->sclust && + dp->fs->Files[i].idx == dp->index) break; + } else { /* Blank entry */ + be = 1; + } + } + if (i == _FS_LOCK) /* The object is not opened */ + return (be || acc == 2) ? FR_OK : FR_TOO_MANY_OPEN_FILES; /* Is there a blank entry for new object? */ + + /* The object has been opened. Reject any open against writing file and all write mode open */ + return (acc || dp->fs->Files[i].ctr == 0x100) ? FR_LOCKED : FR_OK; +} + + +static +int enq_lock (FATFS *fs) /* Check if an entry is available for a new object */ +{ + UINT i; + + for (i = 0; i < _FS_LOCK && fs->Files[i].fs; i++) ; + return (i == _FS_LOCK) ? 0 : 1; +} + + +static +UINT inc_lock ( /* Increment object open counter and returns its index (0:Internal error) */ + DIR_* dp, /* Directory object pointing the file to register or increment */ + int acc /* Desired access (0:Read, 1:Write, 2:Delete/Rename) */ +) +{ + UINT i; + + + for (i = 0; i < _FS_LOCK; i++) { /* Find the object */ + if (dp->fs->Files[i].fs == dp->fs && + dp->fs->Files[i].clu == dp->sclust && + dp->fs->Files[i].idx == dp->index) break; + } + + if (i == _FS_LOCK) { /* Not opened. Register it as new. */ + for (i = 0; i < _FS_LOCK && dp->fs->Files[i].fs; i++) ; + if (i == _FS_LOCK) return 0; /* No free entry to register (int err) */ + dp->fs->Files[i].fs = dp->fs; + dp->fs->Files[i].clu = dp->sclust; + dp->fs->Files[i].idx = dp->index; + dp->fs->Files[i].ctr = 0; + } + + if (acc && dp->fs->Files[i].ctr) return 0; /* Access violation (int err) */ + + dp->fs->Files[i].ctr = acc ? 0x100 : dp->fs->Files[i].ctr + 1; /* Set semaphore value */ + + return i + 1; +} + + +static +FRESULT dec_lock ( /* Decrement object open counter */ + FATFS *fs, + UINT i /* Semaphore index (1..) */ +) +{ + WORD n; + FRESULT res; + + + if (--i < _FS_LOCK) { /* Shift index number origin from 0 */ + n = fs->Files[i].ctr; + if (n == 0x100) n = 0; /* If write mode open, delete the entry */ + if (n) n--; /* Decrement read mode open count */ + fs->Files[i].ctr = n; + if (!n) fs->Files[i].fs = 0; /* Delete the entry if open count gets zero */ + res = FR_OK; + } else { + res = FR_INT_ERR; /* Invalid index nunber */ + } + return res; +} + + +static +void clear_lock ( /* Clear lock entries of the volume */ + FATFS *fs +) +{ + UINT i; + + for (i = 0; i < _FS_LOCK; i++) { + if (fs->Files[i].fs == fs) fs->Files[i].fs = 0; + } +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Move/Flush disk access window in the file system object */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT sync_window ( + FATFS* fs /* File system object */ +) +{ + DWORD wsect; + UINT nf; + + + if (fs->wflag) { /* Write back the sector if it is dirty */ + wsect = fs->winsect; /* Current sector number */ + if (disk_write(fs->drv, fs->win, wsect, 1)) + return FR_DISK_ERR; + fs->wflag = 0; + if (wsect - fs->fatbase < fs->fsize) { /* Is it in the FAT area? */ + for (nf = fs->n_fats; nf >= 2; nf--) { /* Reflect the change to all FAT copies */ + wsect += fs->fsize; + disk_write(fs->drv, fs->win, wsect, 1); + } + } + } + return FR_OK; +} +#endif + + +static +FRESULT move_window ( + FATFS* fs, /* File system object */ + DWORD sector /* Sector number to make appearance in the fs->win[] */ +) +{ + if (sector != fs->winsect) { /* Changed current window */ +#if !_FS_READONLY + if (sync_window(fs) != FR_OK) + return FR_DISK_ERR; +#endif + if (disk_read(fs->drv, fs->win, sector, 1)) + return FR_DISK_ERR; + fs->winsect = sector; + } + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize file system and strage device */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT sync_fs ( /* FR_OK: successful, FR_DISK_ERR: failed */ + FATFS* fs /* File system object */ +) +{ + FRESULT res; + + + res = sync_window(fs); + if (res == FR_OK) { + /* Update FSINFO sector if needed */ + if (fs->fs_type == FS_FAT32 && fs->fsi_flag == 1) { + /* Create FSINFO structure */ + mem_set(fs->win, 0, SS(fs)); + ST_WORD(fs->win+BS_55AA, 0xAA55); + ST_DWORD(fs->win+FSI_LeadSig, 0x41615252); + ST_DWORD(fs->win+FSI_StrucSig, 0x61417272); + ST_DWORD(fs->win+FSI_Free_Count, fs->free_clust); + ST_DWORD(fs->win+FSI_Nxt_Free, fs->last_clust); + /* Write it into the FSINFO sector */ + fs->winsect = fs->volbase + 1; + disk_write(fs->drv, fs->win, fs->winsect, 1); + fs->fsi_flag = 0; + } + /* Make sure that no pending write process in the physical drive */ + if (disk_ioctl(fs->drv, CTRL_SYNC, 0) != RES_OK) + res = FR_DISK_ERR; + } + + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Get sector# from cluster# */ +/*-----------------------------------------------------------------------*/ + + +DWORD clust2sect ( /* !=0: Sector number, 0: Failed - invalid cluster# */ + FATFS* fs, /* File system object */ + DWORD clst /* Cluster# to be converted */ +) +{ + clst -= 2; + if (clst >= (fs->n_fatent - 2)) return 0; /* Invalid cluster# */ + return clst * fs->csize + fs->database; +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Read value of a FAT entry */ +/*-----------------------------------------------------------------------*/ + + +DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, Else:Cluster status */ + FATFS* fs, /* File system object */ + DWORD clst /* Cluster# to get the link information */ +) +{ + UINT wc, bc; + BYTE *p; + + + if (clst < 2 || clst >= fs->n_fatent) /* Check range */ + return 1; + + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; + if (move_window(fs, fs->fatbase + (bc / SS(fs)))) break; + wc = fs->win[bc % SS(fs)]; bc++; + if (move_window(fs, fs->fatbase + (bc / SS(fs)))) break; + wc |= fs->win[bc % SS(fs)] << 8; + return clst & 1 ? wc >> 4 : (wc & 0xFFF); + + case FS_FAT16 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 2)))) break; + p = &fs->win[clst * 2 % SS(fs)]; + return LD_WORD(p); + + case FS_FAT32 : + if (move_window(fs, fs->fatbase + (clst / (SS(fs) / 4)))) break; + p = &fs->win[clst * 4 % SS(fs)]; + return LD_DWORD(p) & 0x0FFFFFFF; + } + + return 0xFFFFFFFF; /* An error occurred at the disk I/O layer */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* FAT access - Change value of a FAT entry */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY + +FRESULT put_fat ( + FATFS* fs, /* File system object */ + DWORD clst, /* Cluster# to be changed in range of 2 to fs->n_fatent - 1 */ + DWORD val /* New value to mark the cluster */ +) +{ + UINT bc; + BYTE *p; + FRESULT res; + + + if (clst < 2 || clst >= fs->n_fatent) { /* Check range */ + res = FR_INT_ERR; + + } else { + switch (fs->fs_type) { + case FS_FAT12 : + bc = (UINT)clst; bc += bc / 2; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = &fs->win[bc % SS(fs)]; + *p = (clst & 1) ? ((*p & 0x0F) | ((BYTE)val << 4)) : (BYTE)val; + bc++; + fs->wflag = 1; + res = move_window(fs, fs->fatbase + (bc / SS(fs))); + if (res != FR_OK) break; + p = &fs->win[bc % SS(fs)]; + *p = (clst & 1) ? (BYTE)(val >> 4) : ((*p & 0xF0) | ((BYTE)(val >> 8) & 0x0F)); + break; + + case FS_FAT16 : + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 2))); + if (res != FR_OK) break; + p = &fs->win[clst * 2 % SS(fs)]; + ST_WORD(p, (WORD)val); + break; + + case FS_FAT32 : + res = move_window(fs, fs->fatbase + (clst / (SS(fs) / 4))); + if (res != FR_OK) break; + p = &fs->win[clst * 4 % SS(fs)]; + val |= LD_DWORD(p) & 0xF0000000; + ST_DWORD(p, val); + break; + + default : + res = FR_INT_ERR; + } + fs->wflag = 1; + } + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Remove a cluster chain */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT remove_chain ( + FATFS* fs, /* File system object */ + DWORD clst /* Cluster# to remove a chain from */ +) +{ + FRESULT res; + DWORD nxt; +#if _USE_ERASE + DWORD scl = clst, ecl = clst, rt[2]; +#endif + + if (clst < 2 || clst >= fs->n_fatent) { /* Check range */ + res = FR_INT_ERR; + + } else { + res = FR_OK; + while (clst < fs->n_fatent) { /* Not a last link? */ + nxt = get_fat(fs, clst); /* Get cluster status */ + if (nxt == 0) break; /* Empty cluster? */ + if (nxt == 1) { res = FR_INT_ERR; break; } /* Internal error? */ + if (nxt == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } /* Disk error? */ + res = put_fat(fs, clst, 0); /* Mark the cluster "empty" */ + if (res != FR_OK) break; + if (fs->free_clust != 0xFFFFFFFF) { /* Update FSINFO */ + fs->free_clust++; + fs->fsi_flag |= 1; + } +#if _USE_ERASE + if (ecl + 1 == nxt) { /* Is next cluster contiguous? */ + ecl = nxt; + } else { /* End of contiguous clusters */ + rt[0] = clust2sect(fs, scl); /* Start sector */ + rt[1] = clust2sect(fs, ecl) + fs->csize - 1; /* End sector */ + disk_ioctl(fs->drv, CTRL_ERASE_SECTOR, rt); /* Erase the block */ + scl = ecl = nxt; + } +#endif + clst = nxt; /* Next cluster */ + } + } + + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Stretch or Create a cluster chain */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +DWORD create_chain ( /* 0:No free cluster, 1:Internal error, 0xFFFFFFFF:Disk error, >=2:New cluster# */ + FATFS* fs, /* File system object */ + DWORD clst /* Cluster# to stretch. 0 means create a new chain. */ +) +{ + DWORD cs, ncl, scl; + FRESULT res; + + + if (clst == 0) { /* Create a new chain */ + scl = fs->last_clust; /* Get suggested start point */ + if (!scl || scl >= fs->n_fatent) scl = 1; + } + else { /* Stretch the current chain */ + cs = get_fat(fs, clst); /* Check the cluster status */ + if (cs < 2) return 1; /* It is an invalid cluster */ + if (cs < fs->n_fatent) return cs; /* It is already followed by next cluster */ + scl = clst; + } + + ncl = scl; /* Start cluster */ + for (;;) { + ncl++; /* Next cluster */ + if (ncl >= fs->n_fatent) { /* Wrap around */ + ncl = 2; + if (ncl > scl) return 0; /* No free cluster */ + } + cs = get_fat(fs, ncl); /* Get the cluster status */ + if (cs == 0) break; /* Found a free cluster */ + if (cs == 0xFFFFFFFF || cs == 1)/* An error occurred */ + return cs; + if (ncl == scl) return 0; /* No free cluster */ + } + + res = put_fat(fs, ncl, 0x0FFFFFFF); /* Mark the new cluster "last link" */ + if (res == FR_OK && clst != 0) { + res = put_fat(fs, clst, ncl); /* Link it to the previous one if needed */ + } + if (res == FR_OK) { + fs->last_clust = ncl; /* Update FSINFO */ + if (fs->free_clust != 0xFFFFFFFF) { + fs->free_clust--; + fs->fsi_flag |= 1; + } + } else { + ncl = (res == FR_DISK_ERR) ? 0xFFFFFFFF : 1; + } + + return ncl; /* Return new cluster number or error code */ +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* FAT handling - Convert offset into cluster with link map table */ +/*-----------------------------------------------------------------------*/ + +#if _USE_FASTSEEK +static +DWORD clmt_clust ( /* <2:Error, >=2:Cluster number */ + FIL* fp, /* Pointer to the file object */ + DWORD ofs /* File offset to be converted to cluster# */ +) +{ + DWORD cl, ncl, *tbl; + + + tbl = fp->cltbl + 1; /* Top of CLMT */ + cl = ofs / SS(fp->fs) / fp->fs->csize; /* Cluster order from top of the file */ + for (;;) { + ncl = *tbl++; /* Number of cluters in the fragment */ + if (!ncl) return 0; /* End of table? (error) */ + if (cl < ncl) break; /* In this fragment? */ + cl -= ncl; tbl++; /* Next fragment */ + } + return cl + *tbl; /* Return the cluster number */ +} +#endif /* _USE_FASTSEEK */ + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Set directory index */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_sdi ( + DIR_* dp, /* Pointer to directory object */ + WORD idx /* Index of directory table */ +) +{ + DWORD clst; + WORD ic; + + + dp->index = idx; + clst = dp->sclust; + if (clst == 1 || clst >= dp->fs->n_fatent) /* Check start cluster range */ + return FR_INT_ERR; + if (!clst && dp->fs->fs_type == FS_FAT32) /* Replace cluster# 0 with root cluster# if in FAT32 */ + clst = dp->fs->dirbase; + + if (clst == 0) { /* Static table (root-directory in FAT12/16) */ + dp->clust = clst; + if (idx >= dp->fs->n_rootdir) /* Index is out of range */ + return FR_INT_ERR; + dp->sect = dp->fs->dirbase + idx / (SS(dp->fs) / SZ_DIR); /* Sector# */ + } + else { /* Dynamic table (sub-dirs or root-directory in FAT32) */ + ic = SS(dp->fs) / SZ_DIR * dp->fs->csize; /* Entries per cluster */ + while (idx >= ic) { /* Follow cluster chain */ + clst = get_fat(dp->fs, clst); /* Get next cluster */ + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; /* Disk error */ + if (clst < 2 || clst >= dp->fs->n_fatent) /* Reached to end of table or int error */ + return FR_INT_ERR; + idx -= ic; + } + dp->clust = clst; + dp->sect = clust2sect(dp->fs, clst) + idx / (SS(dp->fs) / SZ_DIR); /* Sector# */ + } + + dp->dir = dp->fs->win + (idx % (SS(dp->fs) / SZ_DIR)) * SZ_DIR; /* Ptr to the entry in the sector */ + + return FR_OK; /* Seek succeeded */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Move directory table index next */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_next ( /* FR_OK:Succeeded, FR_NO_FILE:End of table, FR_DENIED:Could not stretch */ + DIR_* dp, /* Pointer to the directory object */ + int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ +) +{ + DWORD clst; + WORD i; + + + i = dp->index + 1; + if (!i || !dp->sect) /* Report EOT when index has reached 65535 */ + return FR_NO_FILE; + + if (!(i % (SS(dp->fs) / SZ_DIR))) { /* Sector changed? */ + dp->sect++; /* Next sector */ + + if (!dp->clust) { /* Static table */ + if (i >= dp->fs->n_rootdir) /* Report EOT if it reached end of static table */ + return FR_NO_FILE; + } + else { /* Dynamic table */ + if (((i / (SS(dp->fs) / SZ_DIR)) & (dp->fs->csize - 1)) == 0) { /* Cluster changed? */ + clst = get_fat(dp->fs, dp->clust); /* Get next cluster */ + if (clst <= 1) return FR_INT_ERR; + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; + if (clst >= dp->fs->n_fatent) { /* If it reached end of dynamic table, */ +#if !_FS_READONLY + BYTE c; + if (!stretch) return FR_NO_FILE; /* If do not stretch, report EOT */ + clst = create_chain(dp->fs, dp->clust); /* Stretch cluster chain */ + if (clst == 0) return FR_DENIED; /* No free cluster */ + if (clst == 1) return FR_INT_ERR; + if (clst == 0xFFFFFFFF) return FR_DISK_ERR; + /* Clean-up stretched table */ + if (sync_window(dp->fs)) return FR_DISK_ERR;/* Flush disk access window */ + mem_set(dp->fs->win, 0, SS(dp->fs)); /* Clear window buffer */ + dp->fs->winsect = clust2sect(dp->fs, clst); /* Cluster start sector */ + for (c = 0; c < dp->fs->csize; c++) { /* Fill the new cluster with 0 */ + dp->fs->wflag = 1; + if (sync_window(dp->fs)) return FR_DISK_ERR; + dp->fs->winsect++; + } + dp->fs->winsect -= c; /* Rewind window offset */ +#else + if (!stretch) return FR_NO_FILE; /* If do not stretch, report EOT */ + return FR_NO_FILE; /* Report EOT */ +#endif + } + dp->clust = clst; /* Initialize data for new cluster */ + dp->sect = clust2sect(dp->fs, clst); + } + } + } + + dp->index = i; /* Current index */ + dp->dir = dp->fs->win + (i % (SS(dp->fs) / SZ_DIR)) * SZ_DIR; /* Current entry in the window */ + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Reserve directory entry */ +/*-----------------------------------------------------------------------*/ + +#if !_FS_READONLY +static +FRESULT dir_alloc ( + DIR_* dp, /* Pointer to the directory object */ + UINT nent /* Number of contiguous entries to allocate (1-21) */ +) +{ + FRESULT res; + UINT n; + + + res = dir_sdi(dp, 0); + if (res == FR_OK) { + n = 0; + do { + res = move_window(dp->fs, dp->sect); + if (res != FR_OK) break; + if (dp->dir[0] == DDE || dp->dir[0] == 0) { /* Is it a blank entry? */ + if (++n == nent) break; /* A block of contiguous entries is found */ + } else { + n = 0; /* Not a blank entry. Restart to search */ + } + res = dir_next(dp, 1); /* Next entry with table stretch enabled */ + } while (res == FR_OK); + } + if (res == FR_NO_FILE) res = FR_DENIED; /* No directory entry to allocate */ + return res; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Load/Store start cluster number */ +/*-----------------------------------------------------------------------*/ + +static +DWORD ld_clust ( + FATFS* fs, /* Pointer to the fs object */ + BYTE* dir /* Pointer to the directory entry */ +) +{ + DWORD cl; + + cl = LD_WORD(dir+DIR_FstClusLO); + if (fs->fs_type == FS_FAT32) + cl |= (DWORD)LD_WORD(dir+DIR_FstClusHI) << 16; + + return cl; +} + + +#if !_FS_READONLY +static +void st_clust ( + BYTE* dir, /* Pointer to the directory entry */ + DWORD cl /* Value to be set */ +) +{ + ST_WORD(dir+DIR_FstClusLO, cl); + ST_WORD(dir+DIR_FstClusHI, cl >> 16); +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* LFN handling - Test/Pick/Fit an LFN segment from/to directory entry */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +static +const BYTE LfnOfs[] = {1,3,5,7,9,14,16,18,20,22,24,28,30}; /* Offset of LFN characters in the directory entry */ + + +static +int cmp_lfn ( /* 1:Matched, 0:Not matched */ + WCHAR* lfnbuf, /* Pointer to the LFN to be compared */ + BYTE* dir /* Pointer to the directory entry containing a part of LFN */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + i = ((dir[LDIR_Ord] & ~LLE) - 1) * 13; /* Get offset in the LFN buffer */ + s = 0; wc = 1; + do { + uc = LD_WORD(dir+LfnOfs[s]); /* Pick an LFN character from the entry */ + if (wc) { /* Last character has not been processed */ + wc = ff_wtoupper(uc); /* Convert it to upper case */ + if (i >= _MAX_LFN || wc != ff_wtoupper(lfnbuf[i++])) /* Compare it */ + return 0; /* Not matched */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } while (++s < 13); /* Repeat until all characters in the entry are checked */ + + if ((dir[LDIR_Ord] & LLE) && wc && lfnbuf[i]) /* Last segment matched but different length */ + return 0; + + return 1; /* The part of LFN matched */ +} + + + +static +int pick_lfn ( /* 1:Succeeded, 0:Buffer overflow */ + WCHAR* lfnbuf, /* Pointer to the Unicode-LFN buffer */ + BYTE* dir /* Pointer to the directory entry */ +) +{ + UINT i, s; + WCHAR wc, uc; + + + i = ((dir[LDIR_Ord] & 0x3F) - 1) * 13; /* Offset in the LFN buffer */ + + s = 0; wc = 1; + do { + uc = LD_WORD(dir+LfnOfs[s]); /* Pick an LFN character from the entry */ + if (wc) { /* Last character has not been processed */ + if (i >= _MAX_LFN) return 0; /* Buffer overflow? */ + lfnbuf[i++] = wc = uc; /* Store it */ + } else { + if (uc != 0xFFFF) return 0; /* Check filler */ + } + } while (++s < 13); /* Read all character in the entry */ + + if (dir[LDIR_Ord] & LLE) { /* Put terminator if it is the last LFN part */ + if (i >= _MAX_LFN) return 0; /* Buffer overflow? */ + lfnbuf[i] = 0; + } + + return 1; +} + + +#if !_FS_READONLY +static +void fit_lfn ( + const WCHAR* lfnbuf, /* Pointer to the LFN buffer */ + BYTE* dir, /* Pointer to the directory entry */ + BYTE ord, /* LFN order (1-20) */ + BYTE sum /* SFN sum */ +) +{ + UINT i, s; + WCHAR wc; + + + dir[LDIR_Chksum] = sum; /* Set check sum */ + dir[LDIR_Attr] = AM_LFN; /* Set attribute. LFN entry */ + dir[LDIR_Type] = 0; + ST_WORD(dir+LDIR_FstClusLO, 0); + + i = (ord - 1) * 13; /* Get offset in the LFN buffer */ + s = wc = 0; + do { + if (wc != 0xFFFF) wc = lfnbuf[i++]; /* Get an effective character */ + ST_WORD(dir+LfnOfs[s], wc); /* Put it */ + if (!wc) wc = 0xFFFF; /* Padding characters following last character */ + } while (++s < 13); + if (wc == 0xFFFF || !lfnbuf[i]) ord |= LLE; /* Bottom LFN part is the start of LFN sequence */ + dir[LDIR_Ord] = ord; /* Set the LFN order */ +} + +#endif +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Create numbered name */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +void gen_numname ( + BYTE* dst, /* Pointer to generated SFN */ + const BYTE* src, /* Pointer to source SFN to be modified */ + const WCHAR* lfn, /* Pointer to LFN */ + WORD seq /* Sequence number */ +) +{ + BYTE ns[8], c; + UINT i, j; + + + mem_cpy(dst, src, 11); + + if (seq > 5) { /* On many collisions, generate a hash number instead of sequential number */ + do seq = (seq >> 1) + (seq << 15) + (WORD)*lfn++; while (*lfn); + } + + /* itoa (hexdecimal) */ + i = 7; + do { + c = (seq % 16) + '0'; + if (c > '9') c += 7; + ns[i--] = c; + seq /= 16; + } while (seq); + ns[i] = '~'; + + /* Append the number */ + for (j = 0; j < i && dst[j] != ' '; j++) { + if (IsDBCS1(dst[j])) { + if (j == i - 1) break; + j++; + } + } + do { + dst[j++] = (i < 8) ? ns[i++] : ' '; + } while (j < 8); +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Calculate sum of an SFN */ +/*-----------------------------------------------------------------------*/ +#if _USE_LFN +static +BYTE sum_sfn ( + const BYTE* dir /* Pointer to the SFN entry */ +) +{ + BYTE sum = 0; + UINT n = 11; + + do sum = (sum >> 1) + (sum << 7) + *dir++; while (--n); + return sum; +} +#endif + + + + +/*-----------------------------------------------------------------------*/ +/* Directory handling - Find an object in the directory */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT dir_find ( + DIR_* dp /* Pointer to the directory object linked to the file name */ +) +{ + FRESULT res; + BYTE c, *dir; +#if _USE_LFN + BYTE a, ord, sum; +#endif + + res = dir_sdi(dp, 0); /* Rewind directory object */ + if (res != FR_OK) return res; + +#if _USE_LFN + ord = sum = 0xFF; +#endif + do { + res = move_window(dp->fs, dp->sect); + if (res != FR_OK) break; + dir = dp->dir; /* Ptr to the directory entry of current index */ + c = dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ +#if _USE_LFN /* LFN configuration */ + a = dir[DIR_Attr] & AM_MASK; + if (c == DDE || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (dp->lfn) { + if (c & LLE) { /* Is it start of LFN sequence? */ + sum = dir[LDIR_Chksum]; + c &= ~LLE; ord = c; /* LFN start order */ + dp->lfn_idx = dp->index; + } + /* Check validity of the LFN entry and compare it with given name */ + ord = (c == ord && sum == dir[LDIR_Chksum] && cmp_lfn(dp->lfn, dir)) ? ord - 1 : 0xFF; + } + } else { /* An SFN entry is found */ + if (!ord && sum == sum_sfn(dir)) break; /* LFN matched? */ + ord = 0xFF; dp->lfn_idx = 0xFFFF; /* Reset LFN sequence */ + if (!(dp->fn[NS] & NS_LOSS) && !mem_cmp(dir, dp->fn, 11)) break; /* SFN matched? */ + } + } +#else /* Non LFN configuration */ + if (!(dir[DIR_Attr] & AM_VOL) && !mem_cmp(dir, dp->fn, 11)) /* Is it a valid entry? */ + break; +#endif + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read an object from the directory */ +/*-----------------------------------------------------------------------*/ +#if _FS_MINIMIZE <= 1 || _USE_LABEL || _FS_RPATH >= 2 +static +FRESULT dir_read ( + DIR_* dp, /* Pointer to the directory object */ + int vol /* Filtered by 0:file/directory or 1:volume label */ +) +{ + FRESULT res; + BYTE a, c, *dir; +#if _USE_LFN + BYTE ord = 0xFF, sum = 0xFF; +#endif + + res = FR_NO_FILE; + while (dp->sect) { + res = move_window(dp->fs, dp->sect); + if (res != FR_OK) break; + dir = dp->dir; /* Ptr to the directory entry of current index */ + c = dir[DIR_Name]; + if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */ + a = dir[DIR_Attr] & AM_MASK; +#if _USE_LFN /* LFN configuration */ + if (c == DDE || (!_FS_RPATH && c == '.') || (int)(a == AM_VOL) != vol) { /* An entry without valid data */ + ord = 0xFF; + } else { + if (a == AM_LFN) { /* An LFN entry is found */ + if (c & LLE) { /* Is it start of LFN sequence? */ + sum = dir[LDIR_Chksum]; + c &= ~LLE; ord = c; + dp->lfn_idx = dp->index; + } + /* Check LFN validity and capture it */ + ord = (c == ord && sum == dir[LDIR_Chksum] && pick_lfn(dp->lfn, dir)) ? ord - 1 : 0xFF; + } else { /* An SFN entry is found */ + if (ord || sum != sum_sfn(dir)) /* Is there a valid LFN? */ + dp->lfn_idx = 0xFFFF; /* It has no LFN. */ + break; + } + } +#else /* Non LFN configuration */ + if (c != DDE && (_FS_RPATH || c != '.') && a != AM_LFN && (int)(a == AM_VOL) == vol) /* Is it a valid entry? */ + break; +#endif + res = dir_next(dp, 0); /* Next entry */ + if (res != FR_OK) break; + } + + if (res != FR_OK) dp->sect = 0; + + return res; +} +#endif /* _FS_MINIMIZE <= 1 || _USE_LABEL || _FS_RPATH >= 2 */ + + + + +/*-----------------------------------------------------------------------*/ +/* Register an object to the directory */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY +static +FRESULT dir_register ( /* FR_OK:Successful, FR_DENIED:No free entry or too many SFN collision, FR_DISK_ERR:Disk error */ + DIR_* dp /* Target directory with object name to be created */ +) +{ + FRESULT res; +#if _USE_LFN /* LFN configuration */ + WORD n, ne; + BYTE sn[12], *fn, sum; + WCHAR *lfn; + + + fn = dp->fn; lfn = dp->lfn; + mem_cpy(sn, fn, 12); + + if (_FS_RPATH && (sn[NS] & NS_DOT)) /* Cannot create dot entry */ + return FR_INVALID_NAME; + + if (sn[NS] & NS_LOSS) { /* When LFN is out of 8.3 format, generate a numbered name */ + fn[NS] = 0; dp->lfn = 0; /* Find only SFN */ + for (n = 1; n < 100; n++) { + gen_numname(fn, sn, lfn, n); /* Generate a numbered name */ + res = dir_find(dp); /* Check if the name collides with existing SFN */ + if (res != FR_OK) break; + } + if (n == 100) return FR_DENIED; /* Abort if too many collisions */ + if (res != FR_NO_FILE) return res; /* Abort if the result is other than 'not collided' */ + fn[NS] = sn[NS]; dp->lfn = lfn; + } + + if (sn[NS] & NS_LFN) { /* When LFN is to be created, allocate entries for an SFN + LFNs. */ + for (n = 0; lfn[n]; n++) ; + ne = (n + 25) / 13; + } else { /* Otherwise allocate an entry for an SFN */ + ne = 1; + } + res = dir_alloc(dp, ne); /* Allocate entries */ + + if (res == FR_OK && --ne) { /* Set LFN entry if needed */ + res = dir_sdi(dp, (WORD)(dp->index - ne)); + if (res == FR_OK) { + sum = sum_sfn(dp->fn); /* Sum value of the SFN tied to the LFN */ + do { /* Store LFN entries in bottom first */ + res = move_window(dp->fs, dp->sect); + if (res != FR_OK) break; + fit_lfn(dp->lfn, dp->dir, (BYTE)ne, sum); + dp->fs->wflag = 1; + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK && --ne); + } + } +#else /* Non LFN configuration */ + res = dir_alloc(dp, 1); /* Allocate an entry for SFN */ +#endif + + if (res == FR_OK) { /* Set SFN entry */ + res = move_window(dp->fs, dp->sect); + if (res == FR_OK) { + mem_set(dp->dir, 0, SZ_DIR); /* Clean the entry */ + mem_cpy(dp->dir, dp->fn, 11); /* Put SFN */ +#if _USE_LFN + dp->dir[DIR_NTres] = dp->fn[NS] & (NS_BODY | NS_EXT); /* Put NT flag */ +#endif + dp->fs->wflag = 1; + } + } + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Remove an object from the directory */ +/*-----------------------------------------------------------------------*/ +#if !_FS_READONLY && !_FS_MINIMIZE +static +FRESULT dir_remove ( /* FR_OK: Successful, FR_DISK_ERR: A disk error */ + DIR_* dp /* Directory object pointing the entry to be removed */ +) +{ + FRESULT res; +#if _USE_LFN /* LFN configuration */ + WORD i; + + i = dp->index; /* SFN index */ + res = dir_sdi(dp, (WORD)((dp->lfn_idx == 0xFFFF) ? i : dp->lfn_idx)); /* Goto the SFN or top of the LFN entries */ + if (res == FR_OK) { + do { + res = move_window(dp->fs, dp->sect); + if (res != FR_OK) break; + *dp->dir = DDE; /* Mark the entry "deleted" */ + dp->fs->wflag = 1; + if (dp->index >= i) break; /* When reached SFN, all entries of the object has been deleted. */ + res = dir_next(dp, 0); /* Next entry */ + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR; + } + +#else /* Non LFN configuration */ + res = dir_sdi(dp, dp->index); + if (res == FR_OK) { + res = move_window(dp->fs, dp->sect); + if (res == FR_OK) { + *dp->dir = DDE; /* Mark the entry "deleted" */ + dp->fs->wflag = 1; + } + } +#endif + + return res; +} +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Pick a segment and create the object name in directory form */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT create_name ( + DIR_* dp, /* Pointer to the directory object */ + const /*TCHAR*/char **path /* Pointer to pointer to the segment in the path string */ +) +{ +#if _USE_LFN /* LFN configuration */ + BYTE b, cf; + WCHAR *lfn; + char32_t w; + UINT i, ni, si, di; + const /*TCHAR*/char *p; + + /* Create LFN in Unicode */ + for (p = *path; *p == '/' || *p == '\\'; p++) ; /* Strip duplicated separator */ + lfn = dp->lfn; + /*si =*/ di = 0; + for (;;) { + w = miosix::Unicode::nextUtf8(p);/*p[si++];*/ /* Get a character */ + if(w == miosix::Unicode::invalid || w > 0xffff) return FR_INVALID_NAME; + if (w < ' ' || w == '/' || w == '\\') break; /* Break on end of segment */ + if (di >= _MAX_LFN) /* Reject too long name */ + return FR_INVALID_NAME; +#if !_LFN_UNICODE + #error "Unsupported" + w &= 0xFF; + if (IsDBCS1(w)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */ + b = (BYTE)p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(b)) + return FR_INVALID_NAME; /* Reject invalid sequence */ + w = (w << 8) + b; /* Create a DBC */ + } + w = ff_convert(w, 1); /* Convert ANSI/OEM to Unicode */ + if (!w) return FR_INVALID_NAME; /* Reject invalid code */ +#endif + if (w < 0x80 && chk_chr("\"*:<>\?|\x7F", w)) /* Reject illegal characters for LFN */ + return FR_INVALID_NAME; + lfn[di++] = w; /* Store the Unicode character */ + } + *path = p/*&p[si]*/; /* Return pointer to the next segment */ + cf = (w < ' ') ? NS_LAST : 0; /* Set last segment flag if end of path */ +#if _FS_RPATH + if ((di == 1 && lfn[di-1] == '.') || /* Is this a dot entry? */ + (di == 2 && lfn[di-1] == '.' && lfn[di-2] == '.')) { + lfn[di] = 0; + for (i = 0; i < 11; i++) + dp->fn[i] = (i < di) ? '.' : ' '; + dp->fn[i] = cf | NS_DOT; /* This is a dot entry */ + return FR_OK; + } +#endif + while (di) { /* Strip trailing spaces and dots */ + w = lfn[di-1]; + if (w != ' ' && w != '.') break; + di--; + } + if (!di) return FR_INVALID_NAME; /* Reject nul string */ + + lfn[di] = 0; /* LFN is created */ + + /* Create SFN in directory form */ + mem_set(dp->fn, ' ', 11); + for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ; /* Strip leading spaces and dots */ + if (si) cf |= NS_LOSS | NS_LFN; + while (di && lfn[di - 1] != '.') di--; /* Find extension (di<=si: no extension) */ + + b = i = 0; ni = 8; + for (;;) { + w = lfn[si++]; /* Get an LFN character */ + if (!w) break; /* Break on end of the LFN */ + if (w == ' ' || (w == '.' && si != di)) { /* Remove spaces and dots */ + cf |= NS_LOSS | NS_LFN; continue; + } + + if (i >= ni || si == di) { /* Extension or end of SFN */ + if (ni == 11) { /* Long extension */ + cf |= NS_LOSS | NS_LFN; break; + } + if (si != di) cf |= NS_LOSS | NS_LFN; /* Out of 8.3 format */ + if (si > di) break; /* No extension */ + si = di; i = 8; ni = 11; /* Enter extension section */ + b <<= 2; continue; + } + + if (w >= 0x80) { /* Non ASCII character */ +#ifdef _EXCVT + w = ff_convert(w, 0); /* Unicode -> OEM code */ + if (w) w = ExCvt[w - 0x80]; /* Convert extended character to upper (SBCS) */ +#else + w = ff_convert(ff_wtoupper(w), 0); /* Upper converted Unicode -> OEM code */ +#endif + cf |= NS_LFN; /* Force create LFN entry */ + } + + if (_DF1S && w >= 0x100) { /* Double byte character (always false on SBCS cfg) */ + if (i >= ni - 1) { + cf |= NS_LOSS | NS_LFN; i = ni; continue; + } + dp->fn[i++] = (BYTE)(w >> 8); + } else { /* Single byte character */ + if (!w || chk_chr("+,;=[]", w)) { /* Replace illegal characters for SFN */ + w = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ + } else { + if (IsUpper(w)) { /* ASCII large capital */ + b |= 2; + } else { + if (IsLower(w)) { /* ASCII small capital */ + b |= 1; w -= 0x20; + } + } + } + } + dp->fn[i++] = (BYTE)w; + } + + if (dp->fn[0] == DDE) dp->fn[0] = NDDE; /* If the first character collides with deleted mark, replace it with 0x05 */ + + if (ni == 8) b <<= 2; + if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) /* Create LFN entry when there are composite capitals */ + cf |= NS_LFN; + if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */ + if ((b & 0x03) == 0x01) cf |= NS_EXT; /* NT flag (Extension has only small capital) */ + if ((b & 0x0C) == 0x04) cf |= NS_BODY; /* NT flag (Filename has only small capital) */ + } + + dp->fn[NS] = cf; /* SFN is created */ + + return FR_OK; + + +#else /* Non-LFN configuration */ + BYTE b, c, d, *sfn; + UINT ni, si, i; + const char *p; + + /* Create file name in directory form */ + for (p = *path; *p == '/' || *p == '\\'; p++) ; /* Strip duplicated separator */ + sfn = dp->fn; + mem_set(sfn, ' ', 11); + si = i = b = 0; ni = 8; +#if _FS_RPATH + if (p[si] == '.') { /* Is this a dot entry? */ + for (;;) { + c = (BYTE)p[si++]; + if (c != '.' || si >= 3) break; + sfn[i++] = c; + } + if (c != '/' && c != '\\' && c > ' ') return FR_INVALID_NAME; + *path = &p[si]; /* Return pointer to the next segment */ + sfn[NS] = (c <= ' ') ? NS_LAST | NS_DOT : NS_DOT; /* Set last segment flag if end of path */ + return FR_OK; + } +#endif + for (;;) { + c = (BYTE)p[si++]; + if (c <= ' ' || c == '/' || c == '\\') break; /* Break on end of segment */ + if (c == '.' || i >= ni) { + if (ni != 8 || c != '.') return FR_INVALID_NAME; + i = 8; ni = 11; + b <<= 2; continue; + } + if (c >= 0x80) { /* Extended character? */ + b |= 3; /* Eliminate NT flag */ +#ifdef _EXCVT + c = ExCvt[c - 0x80]; /* To upper extended characters (SBCS cfg) */ +#else +#if !_DF1S + return FR_INVALID_NAME; /* Reject extended characters (ASCII cfg) */ +#endif +#endif + } + if (IsDBCS1(c)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */ + d = (BYTE)p[si++]; /* Get 2nd byte */ + if (!IsDBCS2(d) || i >= ni - 1) /* Reject invalid DBC */ + return FR_INVALID_NAME; + sfn[i++] = c; + sfn[i++] = d; + } else { /* Single byte code */ + if (chk_chr("\"*+,:;<=>\?[]|\x7F", c)) /* Reject illegal chrs for SFN */ + return FR_INVALID_NAME; + if (IsUpper(c)) { /* ASCII large capital? */ + b |= 2; + } else { + if (IsLower(c)) { /* ASCII small capital? */ + b |= 1; c -= 0x20; + } + } + sfn[i++] = c; + } + } + *path = &p[si]; /* Return pointer to the next segment */ + c = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of path */ + + if (!i) return FR_INVALID_NAME; /* Reject nul string */ + if (sfn[0] == DDE) sfn[0] = NDDE; /* When first character collides with DDE, replace it with 0x05 */ + + if (ni == 8) b <<= 2; + if ((b & 0x03) == 0x01) c |= NS_EXT; /* NT flag (Name extension has only small capital) */ + if ((b & 0x0C) == 0x04) c |= NS_BODY; /* NT flag (Name body has only small capital) */ + + sfn[NS] = c; /* Store NT flag, File name is created */ + + return FR_OK; +#endif +} + + + + +/*-----------------------------------------------------------------------*/ +/* Get file information from directory entry */ +/*-----------------------------------------------------------------------*/ +#if _FS_MINIMIZE <= 1 || _FS_RPATH >= 2 +static +void get_fileinfo ( /* No return code */ + DIR_* dp, /* Pointer to the directory object */ + FILINFO* fno /* Pointer to the file information to be filled */ +) +{ + UINT i; + TCHAR *p, c; + + + p = fno->fname; + if (dp->sect) { /* Get SFN */ + BYTE *dir = dp->dir; + + i = 0; + while (i < 11) { /* Copy name body and extension */ + c = (TCHAR)dir[i++]; + if (c == ' ') continue; /* Skip padding spaces */ + if (c == NDDE) c = (TCHAR)DDE; /* Restore replaced DDE character */ + if (i == 9) *p++ = '.'; /* Insert a . if extension is exist */ +#if _USE_LFN + if (IsUpper(c) && (dir[DIR_NTres] & (i >= 9 ? NS_EXT : NS_BODY))) + c += 0x20; /* To lower */ +#if _LFN_UNICODE + if (IsDBCS1(c) && i != 8 && i != 11 && IsDBCS2(dir[i])) + c = c << 8 | dir[i++]; + c = ff_convert(c, 1); /* OEM -> Unicode */ + if (!c) c = '?'; +#endif +#endif + *p++ = c; + } + fno->fattrib = dir[DIR_Attr]; /* Attribute */ + fno->fsize = LD_DWORD(dir+DIR_FileSize); /* Size */ + fno->fdate = LD_WORD(dir+DIR_WrtDate); /* Date */ + fno->ftime = LD_WORD(dir+DIR_WrtTime); /* Time */ + fno->inode=INODE(dp); + } + *p = 0; /* Terminate SFN string by a \0 */ + +#if _USE_LFN + if (fno->lfname) { + WCHAR w, *lfn; + char *pp; + + i = 0; pp = fno->lfname; + if (dp->sect && fno->lfsize && dp->lfn_idx != 0xFFFF) { /* Get LFN if available */ + lfn = dp->lfn; + while ((w = *lfn++) != 0) { /* Get an LFN character */ +#if !_LFN_UNICODE + #error "unsupported" + w = ff_convert(w, 0); /* Unicode -> OEM */ + if (!w) { i = 0; break; } /* No LFN if it could not be converted */ + if (_DF1S && w >= 0x100) /* Put 1st byte if it is a DBC (always false on SBCS cfg) */ + pp[i++] = (TCHAR)(w >> 8); +#endif + std::pair result; + result = miosix::Unicode::putUtf8(&pp[i],w,fno->lfsize - 1 - i); + if(result.first != miosix::Unicode::OK) { i = 0; break; } /* No LFN if buffer overflow */ + i += result.second; + } + } + pp[i] = 0; /* Terminate LFN string by a \0 */ + + //By TFT: unlike plain FatFs we always want to fill lfname with an + //utf8-encoded file name. If there is no lfn (or is too long or + //otherwise broken), then take the sfn (which is utf16) and copy it to + //lfname, converting it to utf8 in the process + if(pp[0]==0) + { + lfn = fno->fname; + i = 0; + while ((w = *lfn++) != 0) + { + std::pair result; + result = miosix::Unicode::putUtf8(&pp[i],w,fno->lfsize - 1 - i); + if(result.first != miosix::Unicode::OK) { i = 0; break; } /* No LFN if buffer overflow */ + i += result.second; + } + pp[i] = 0; /* Terminate LFN string by a \0 */ + } + } +#endif +} +#endif /* _FS_MINIMIZE <= 1 || _FS_RPATH >= 2*/ + + + + +/*-----------------------------------------------------------------------*/ +/* Get logical drive number from path name */ +/*-----------------------------------------------------------------------*/ + +//By TFT: We don't want this reminiscence of microsoft OSes identifying +//drive numbers with 0:/path/to/file +// static +// int get_ldnumber ( /* Returns logical drive number (-1:invalid drive) */ +// const TCHAR** path /* Pointer to pointer to the path name */ +// ) +// { +// int vol = -1; +// +// +// if (*path) { +// vol = (*path)[0] - '0'; +// if ((UINT)vol < 9 && (*path)[1] == ':') { /* There is a drive number */ +// *path += 2; /* Get value and strip it */ +// if (vol >= _VOLUMES) vol = -1; /* Check if the drive number is valid */ +// } else { /* No drive number use default drive */ +// #if _FS_RPATH && _VOLUMES >= 2 +// vol = CurrVol; /* Current drive */ +// #else +// vol = 0; /* Drive 0 */ +// #endif +// } +// } +// +// return vol; +// } + + + + +/*-----------------------------------------------------------------------*/ +/* Follow a file path */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ + DIR_* dp, /* Directory object to return last directory and found object */ + const /*TCHAR*/char *path /* Full-path string to find a file or directory */ +) +{ + FRESULT res; + BYTE *dir, ns; + + +#if _FS_RPATH + if (*path == '/' || *path == '\\') { /* There is a heading separator */ + path++; dp->sclust = 0; /* Strip it and start from the root directory */ + } else { /* No heading separator */ + dp->sclust = dp->fs->cdir; /* Start from the current directory */ + } +#else + if (*path == '/' || *path == '\\') /* Strip heading separator if exist */ + path++; + dp->sclust = 0; /* Always start from the root directory */ +#endif + + if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */ + res = dir_sdi(dp, 0); + dp->dir = 0; + } else { /* Follow path */ + for (;;) { + res = create_name(dp, &path); /* Get a segment name of the path */ + if (res != FR_OK) break; + res = dir_find(dp); /* Find an object with the sagment name */ + ns = dp->fn[NS]; + if (res != FR_OK) { /* Failed to find the object */ + if (res == FR_NO_FILE) { /* Object is not found */ + if (_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, */ + dp->sclust = 0; dp->dir = 0; /* it is the root directory and stay there */ + if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */ + res = FR_OK; /* Ended at the root directroy. Function completed. */ + } else { /* Could not find the object */ + if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */ + } + } + break; + } + if (ns & NS_LAST) break; /* Last segment matched. Function completed. */ + dir = dp->dir; /* Follow the sub-directory */ + if (!(dir[DIR_Attr] & AM_DIR)) { /* It is not a sub-directory and cannot follow */ + res = FR_NO_PATH; break; + } + dp->sclust = ld_clust(dp->fs, dir); + } + } + + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load a sector and check if it is an FAT boot sector */ +/*-----------------------------------------------------------------------*/ + +static +BYTE check_fs ( /* 0:FAT boor sector, 1:Valid boor sector but not FAT, 2:Not a boot sector, 3:Disk error */ + FATFS* fs, /* File system object */ + DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */ +) +{ + fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */ + if (move_window(fs, sect) != FR_OK) /* Load boot record */ + return 3; + + if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */ + return 2; + + if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */ + return 0; + if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */ + return 0; + + return 1; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Find logical drive and check if the volume is mounted */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT find_volume ( /* FR_OK(0): successful, !=0: any error occurred */ + FATFS *fs, /* By TFT: added to get rid of static variables */ + /*FATFS** rfs,*/ /* Pointer to pointer to the found file system object */ + /*const TCHAR** path,*/ /* Pointer to pointer to the path name (drive number) */ + BYTE wmode /* !=0: Check write protection for write access */ +) +{ + BYTE fmt; + int vol; + DSTATUS stat; + DWORD bsect, fasize, tsect, sysect, nclst, szbfat; + WORD nrsv; + //FATFS *fs; + + + /* Get logical drive number from the path name */ + //*rfs = 0; + vol = 0;//get_ldnumber(path); + if (vol < 0) return FR_INVALID_DRIVE; + + /* Check if the file system object is valid or not */ + //fs = FatFs[vol]; /* Get pointer to the file system object */ + if (!fs) return FR_NOT_ENABLED; /* Is the file system object available? */ + + ENTER_FF(fs); /* Lock the volume */ + //*rfs = fs; /* Return pointer to the file system object */ + + if (fs->fs_type) { /* If the volume has been mounted */ + stat = RES_OK;//disk_status(fs->drv); + if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */ + if (!_FS_READONLY && wmode && (stat & STA_PROTECT)) /* Check write protection if needed */ + return FR_WRITE_PROTECTED; + return FR_OK; /* The file system object is valid */ + } + } + + /* The file system object is not valid. */ + /* Following code attempts to mount the volume. (analyze BPB and initialize the fs object) */ + + fs->fs_type = 0; /* Clear the file system object */ + //fs->drv = LD2PD(vol); /* Bind the logical drive and a physical drive */ + stat = RES_OK;//disk_initialize(fs->drv); /* Initialize the physical drive */ + if (stat & STA_NOINIT) /* Check if the initialization succeeded */ + return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */ + if (!_FS_READONLY && wmode && (stat & STA_PROTECT)) /* Check disk write protection if needed */ + return FR_WRITE_PROTECTED; +#if _MAX_SS != 512 /* Get sector size (variable sector size cfg only) */ + if (disk_ioctl(fs->drv, GET_SECTOR_SIZE, &fs->ssize) != RES_OK) + return FR_DISK_ERR; +#endif + /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */ + bsect = 0; + fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT boot sector as SFD */ + if (fmt == 1 || (!fmt && (LD2PT(vol)))) { /* Not an FAT boot sector or forced partition number */ + UINT i; + DWORD br[4]; + + for (i = 0; i < 4; i++) { /* Get partition offset */ + BYTE *pt = fs->win+MBR_Table + i * SZ_PTE; + br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0; + } + i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */ + if (i) i--; + do { /* Find an FAT volume */ + bsect = br[i]; + fmt = bsect ? check_fs(fs, bsect) : 2; /* Check the partition */ + } while (!LD2PT(vol) && fmt && ++i < 4); + } + if (fmt == 3) return FR_DISK_ERR; /* An error occured in the disk I/O layer */ + if (fmt) return FR_NO_FILESYSTEM; /* No FAT volume is found */ + + /* An FAT volume is found. Following code initializes the file system object */ + + if (LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs)) /* (BPB_BytsPerSec must be equal to the physical sector size) */ + return FR_NO_FILESYSTEM; + + fasize = LD_WORD(fs->win+BPB_FATSz16); /* Number of sectors per FAT */ + if (!fasize) fasize = LD_DWORD(fs->win+BPB_FATSz32); + fs->fsize = fasize; + + fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FAT copies */ + if (fs->n_fats != 1 && fs->n_fats != 2) /* (Must be 1 or 2) */ + return FR_NO_FILESYSTEM; + fasize *= fs->n_fats; /* Number of sectors for FAT area */ + + fs->csize = fs->win[BPB_SecPerClus]; /* Number of sectors per cluster */ + if (!fs->csize || (fs->csize & (fs->csize - 1))) /* (Must be power of 2) */ + return FR_NO_FILESYSTEM; + + fs->n_rootdir = LD_WORD(fs->win+BPB_RootEntCnt); /* Number of root directory entries */ + if (fs->n_rootdir % (SS(fs) / SZ_DIR)) /* (Must be sector aligned) */ + return FR_NO_FILESYSTEM; + + tsect = LD_WORD(fs->win+BPB_TotSec16); /* Number of sectors on the volume */ + if (!tsect) tsect = LD_DWORD(fs->win+BPB_TotSec32); + + nrsv = LD_WORD(fs->win+BPB_RsvdSecCnt); /* Number of reserved sectors */ + if (!nrsv) return FR_NO_FILESYSTEM; /* (Must not be 0) */ + + /* Determine the FAT sub type */ + sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZ_DIR); /* RSV+FAT+DIR */ + if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ + if (!nclst) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ + fmt = FS_FAT12; + if (nclst >= MIN_FAT16) fmt = FS_FAT16; + if (nclst >= MIN_FAT32) fmt = FS_FAT32; + + /* Boundaries and Limits */ + fs->n_fatent = nclst + 2; /* Number of FAT entries */ + fs->volbase = bsect; /* Volume start sector */ + fs->fatbase = bsect + nrsv; /* FAT start sector */ + fs->database = bsect + sysect; /* Data start sector */ + if (fmt == FS_FAT32) { + if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ + fs->dirbase = LD_DWORD(fs->win+BPB_RootClus); /* Root directory start cluster */ + szbfat = fs->n_fatent * 4; /* (Required FAT size) */ + } else { + if (!fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must not be 0) */ + fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ + szbfat = (fmt == FS_FAT16) ? /* (Required FAT size) */ + fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); + } + if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) /* (BPB_FATSz must not be less than required) */ + return FR_NO_FILESYSTEM; + +#if !_FS_READONLY + /* Initialize cluster allocation information */ + fs->last_clust = fs->free_clust = 0xFFFFFFFF; + + /* Get fsinfo if available */ + fs->fsi_flag = 0x80; + if (fmt == FS_FAT32 /* Enable FSINFO only if FAT32 and BPB_FSInfo is 1 */ + && LD_WORD(fs->win+BPB_FSInfo) == 1 + && move_window(fs, bsect + 1) == FR_OK) + { + fs->fsi_flag = 0; + if (LD_WORD(fs->win+BS_55AA) == 0xAA55 /* Load FSINFO data if available */ + && LD_DWORD(fs->win+FSI_LeadSig) == 0x41615252 + && LD_DWORD(fs->win+FSI_StrucSig) == 0x61417272) + { +#if !_FS_NOFSINFO + fs->free_clust = LD_DWORD(fs->win+FSI_Free_Count); +#endif + fs->last_clust = LD_DWORD(fs->win+FSI_Nxt_Free); + } + } +#endif + fs->fs_type = fmt; /* FAT sub-type */ + fs->id = miosix::atomicAddExchange(&Fsid,1)/*++Fsid*/; /* File system mount ID */ +#if _FS_RPATH + fs->cdir = 0; /* Current directory (root dir) */ +#endif +#if _FS_LOCK /* Clear file lock semaphores */ + clear_lock(fs); +#endif + + return FR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Check if the file/directory object is valid or not */ +/*-----------------------------------------------------------------------*/ + +static +FRESULT validate ( /* FR_OK(0): The object is valid, !=0: Invalid */ + void* obj /* Pointer to the object FIL/DIR to check validity */ +) +{ + FIL *fil = (FIL*)obj; /* Assuming offset of .fs and .id in the FIL/DIR structure is identical */ + + + if (!fil || !fil->fs || !fil->fs->fs_type || fil->fs->id != fil->id) + return FR_INVALID_OBJECT; + + ENTER_FF(fil->fs); /* Lock file system */ + + //if (disk_status(fil->fs->drv) & STA_NOINIT) + // return FR_NOT_READY; + + return FR_OK; +} + + + + +/*-------------------------------------------------------------------------- + + Public Functions + +--------------------------------------------------------------------------*/ + + + +/*-----------------------------------------------------------------------*/ +/* Mount/Unmount a Logical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mount ( + FATFS* fs, /* Pointer to the file system object (NULL:unmount)*/ + /*const TCHAR* path,*/ /* Logical drive number to be mounted/unmounted */ + BYTE opt, /* 0:Do not mount (delayed mount), 1:Mount immediately */ + bool umount +) +{ + FATFS *cfs; + int vol; + FRESULT res; + + + vol = 0;//get_ldnumber(&path); + if (vol < 0) return FR_INVALID_DRIVE; + cfs = fs;//FatFs[vol]; /* Pointer to fs object */ + + if (/*cfs*/umount) { +#if _FS_LOCK + clear_lock(cfs); +#endif +#if _FS_REENTRANT /* Discard sync object of the current volume */ + if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR; +#endif + cfs->fs_type = 0; /* Clear old fs object */ + } + + if (/*fs*/!umount) { + fs->fs_type = 0; /* Clear new fs object */ + memset(fs->Files,0,sizeof(FATFS::Files)); +#if _FS_REENTRANT /* Create sync object for the new volume */ + if (!ff_cre_syncobj(vol, &fs->sobj)) return FR_INT_ERR; +#endif + } + //FatFs[vol] = fs; /* Register new fs object */ + + if (/*!fs*/umount || opt != 1) return FR_OK; /* Do not mount now, it will be mounted later */ + + res = find_volume(fs, /*&path,*/ 0); /* Force mounted the volume */ + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Open or Create a File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_open ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + FIL* fp, /* Pointer to the blank file object */ + const /*TCHAR*/char *path, /* Pointer to the file name */ + BYTE mode /* Access mode and file open mode flags */ +) +{ + FRESULT res; + DIR_ dj; + BYTE *dir; + DEF_NAMEBUF; + + + if (!fp) return FR_INVALID_OBJECT; + fp->fs = 0; /* Clear file object */ + + /* Get logical drive number */ + dj.fs=fs; +#if !_FS_READONLY + mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW; + res = find_volume(dj.fs, /*&path,*/ (BYTE)(mode & ~FA_READ)); +#else + mode &= FA_READ; + res = find_volume(dj.fs, &path, 0); +#endif + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + dir = dj.dir; +#if !_FS_READONLY /* R/W configuration */ + if (res == FR_OK) { + if (!dir) /* Default directory itself */ + res = FR_INVALID_NAME; +#if _FS_LOCK + else + res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); +#endif + } + /* Create or Open a file */ + if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { + DWORD dw, cl; + + if (res != FR_OK) { /* No file, create new */ + if (res == FR_NO_FILE) /* There is no file to open, create a new entry */ +#if _FS_LOCK + res = enq_lock(dj.fs) ? dir_register(&dj) : FR_TOO_MANY_OPEN_FILES; +#else + res = dir_register(&dj); +#endif + mode |= FA_CREATE_ALWAYS; /* File is created */ + dir = dj.dir; /* New entry */ + } + else { /* Any object is already existing */ + if (dir[DIR_Attr] & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ + res = FR_DENIED; + } else { + if (mode & FA_CREATE_NEW) /* Cannot create as new file */ + res = FR_EXIST; + } + } + if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate it if overwrite mode */ + dw = get_fattime(); /* Created time */ + ST_DWORD(dir+DIR_CrtTime, dw); + dir[DIR_Attr] = 0; /* Reset attribute */ + ST_DWORD(dir+DIR_FileSize, 0); /* size = 0 */ + cl = ld_clust(dj.fs, dir); /* Get start cluster */ + st_clust(dir, 0); /* cluster = 0 */ + dj.fs->wflag = 1; + if (cl) { /* Remove the cluster chain if exist */ + dw = dj.fs->winsect; + res = remove_chain(dj.fs, cl); + if (res == FR_OK) { + dj.fs->last_clust = cl - 1; /* Reuse the cluster hole */ + res = move_window(dj.fs, dw); + } + } + } + } + else { /* Open an existing file */ + if (res == FR_OK) { /* Follow succeeded */ + if (dir[DIR_Attr] & AM_DIR) { /* It is a directory */ + res = FR_NO_FILE; + } else { + if ((mode & FA_WRITE) && (dir[DIR_Attr] & AM_RDO)) /* R/O violation */ + res = FR_DENIED; + } + } + } + if (res == FR_OK) { + if (mode & FA_CREATE_ALWAYS) /* Set file change flag if created or overwritten */ + mode |= FA__WRITTEN; + fp->dir_sect = dj.fs->winsect; /* Pointer to the directory entry */ + fp->dir_ptr = dir; +#if _FS_LOCK + fp->lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); + if (!fp->lockid) res = FR_INT_ERR; +#endif + } + +#else /* R/O configuration */ + if (res == FR_OK) { /* Follow succeeded */ + dir = dj.dir; + if (!dir) { /* Current directory itself */ + res = FR_INVALID_NAME; + } else { + if (dir[DIR_Attr] & AM_DIR) /* It is a directory */ + res = FR_NO_FILE; + } + } +#endif + FREE_BUF(); + + if (res == FR_OK) { + fp->flag = mode; /* File access mode */ + fp->err = 0; /* Clear error flag */ + fp->sclust = ld_clust(dj.fs, dir); /* File start cluster */ + fp->fsize = LD_DWORD(dir+DIR_FileSize); /* File size */ + fp->fptr = 0; /* File pointer */ + fp->dsect = 0; +#if _USE_FASTSEEK + fp->cltbl = 0; /* Normal seek mode */ +#endif + fp->fs = dj.fs; /* Validate file object */ + fp->id = fp->fs->id; + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_read ( + FIL* fp, /* Pointer to the file object */ + void* buff, /* Pointer to data buffer */ + UINT btr, /* Number of bytes to read */ + UINT* br /* Pointer to number of bytes read */ +) +{ + FRESULT res; + DWORD clst, sect, remain; + UINT rcnt, cc; + BYTE csect, *rbuff = (BYTE*)buff; + + + *br = 0; /* Clear read byte counter */ + + res = validate(fp); /* Check validity */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->err) /* Check error */ + LEAVE_FF(fp->fs, (FRESULT)fp->err); + if (!(fp->flag & FA_READ)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + remain = fp->fsize - fp->fptr; + if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ + + for ( ; btr; /* Repeat until all data read */ + rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* Sector offset in the cluster */ + if (!csect) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->sclust; /* Follow from the origin */ + } else { /* Middle or end of the file */ +#if _USE_FASTSEEK + if (fp->cltbl) + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + else +#endif + clst = get_fat(fp->fs, fp->clust); /* Follow cluster chain on the FAT */ + } + if (clst < 2) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + sect = clust2sect(fp->fs, fp->clust); /* Get current sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += csect; + cc = btr / SS(fp->fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Read maximum contiguous sectors directly */ + if (csect + cc > fp->fs->csize) /* Clip at cluster boundary */ + cc = fp->fs->csize - csect; + if (disk_read(fp->fs->drv, rbuff, sect, cc)) + ABORT(fp->fs, FR_DISK_ERR); +#if !_FS_READONLY && _FS_MINIMIZE <= 2 /* Replace one of the read sectors with cached data if it contains a dirty sector */ +#if _FS_TINY + if (fp->fs->wflag && fp->fs->winsect - sect < cc) + mem_cpy(rbuff + ((fp->fs->winsect - sect) * SS(fp->fs)), fp->fs->win, SS(fp->fs)); +#else + if ((fp->flag & FA__DIRTY) && fp->dsect - sect < cc) + mem_cpy(rbuff + ((fp->dsect - sect) * SS(fp->fs)), fp->buf, SS(fp->fs)); +#endif +#endif + rcnt = SS(fp->fs) * cc; /* Number of bytes transferred */ + continue; + } +#if !_FS_TINY + if (fp->dsect != sect) { /* Load data sector if not in cache */ +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1)) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + if (disk_read(fp->fs->drv, fp->buf, sect, 1)) /* Fill sector cache */ + ABORT(fp->fs, FR_DISK_ERR); + } +#endif + fp->dsect = sect; + } + rcnt = SS(fp->fs) - ((UINT)fp->fptr % SS(fp->fs)); /* Get partial sector data from sector buffer */ + if (rcnt > btr) rcnt = btr; +#if _FS_TINY + if (move_window(fp->fs, fp->dsect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + mem_cpy(rbuff, &fp->fs->win[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */ +#else + mem_cpy(rbuff, &fp->buf[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */ +#endif + } + + LEAVE_FF(fp->fs, FR_OK); +} + + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Write File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_write ( + FIL* fp, /* Pointer to the file object */ + const void *buff, /* Pointer to the data to be written */ + UINT btw, /* Number of bytes to write */ + UINT* bw /* Pointer to number of bytes written */ +) +{ + FRESULT res; + DWORD clst, sect; + UINT wcnt, cc; + const BYTE *wbuff = (const BYTE*)buff; + BYTE csect; + + + *bw = 0; /* Clear write byte counter */ + + res = validate(fp); /* Check validity */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->err) /* Check error */ + LEAVE_FF(fp->fs, (FRESULT)fp->err); + if (!(fp->flag & FA_WRITE)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + if (fp->fptr + btw < fp->fptr) btw = 0; /* File size cannot reach 4GB */ + + for ( ; btw; /* Repeat until all data written */ + wbuff += wcnt, fp->fptr += wcnt, *bw += wcnt, btw -= wcnt) { + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* Sector offset in the cluster */ + if (!csect) { /* On the cluster boundary? */ + if (fp->fptr == 0) { /* On the top of the file? */ + clst = fp->sclust; /* Follow from the origin */ + if (clst == 0) /* When no cluster is allocated, */ + fp->sclust = clst = create_chain(fp->fs, 0); /* Create a new cluster chain */ + } else { /* Middle or end of the file */ +#if _USE_FASTSEEK + if (fp->cltbl) + clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ + else +#endif + clst = create_chain(fp->fs, fp->clust); /* Follow or stretch cluster chain on the FAT */ + } + if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ + if (clst == 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } +#if _FS_TINY + if (fp->fs->winsect == fp->dsect && sync_window(fp->fs)) /* Write-back sector cache */ + ABORT(fp->fs, FR_DISK_ERR); +#else + if (fp->flag & FA__DIRTY) { /* Write-back sector cache */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1)) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + sect = clust2sect(fp->fs, fp->clust); /* Get current sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += csect; + cc = btw / SS(fp->fs); /* When remaining bytes >= sector size, */ + if (cc) { /* Write maximum contiguous sectors directly */ + if (csect + cc > fp->fs->csize) /* Clip at cluster boundary */ + cc = fp->fs->csize - csect; + if (disk_write(fp->fs->drv, wbuff, sect, cc)) + ABORT(fp->fs, FR_DISK_ERR); +#if _FS_MINIMIZE <= 2 +#if _FS_TINY + if (fp->fs->winsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + mem_cpy(fp->fs->win, wbuff + ((fp->fs->winsect - sect) * SS(fp->fs)), SS(fp->fs)); + fp->fs->wflag = 0; + } +#else + if (fp->dsect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ + mem_cpy(fp->buf, wbuff + ((fp->dsect - sect) * SS(fp->fs)), SS(fp->fs)); + fp->flag &= ~FA__DIRTY; + } +#endif +#endif + wcnt = SS(fp->fs) * cc; /* Number of bytes transferred */ + continue; + } +#if _FS_TINY + if (fp->fptr >= fp->fsize) { /* Avoid silly cache filling at growing edge */ + if (sync_window(fp->fs)) ABORT(fp->fs, FR_DISK_ERR); + fp->fs->winsect = sect; + } +#else + if (fp->dsect != sect) { /* Fill sector cache with file data */ + if (fp->fptr < fp->fsize && + disk_read(fp->fs->drv, fp->buf, sect, 1)) + ABORT(fp->fs, FR_DISK_ERR); + } +#endif + fp->dsect = sect; + } + wcnt = SS(fp->fs) - ((UINT)fp->fptr % SS(fp->fs));/* Put partial sector into file I/O buffer */ + if (wcnt > btw) wcnt = btw; +#if _FS_TINY + if (move_window(fp->fs, fp->dsect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + mem_cpy(&fp->fs->win[fp->fptr % SS(fp->fs)], wbuff, wcnt); /* Fit partial sector */ + fp->fs->wflag = 1; +#else + mem_cpy(&fp->buf[fp->fptr % SS(fp->fs)], wbuff, wcnt); /* Fit partial sector */ + fp->flag |= FA__DIRTY; +#endif + } + + if (fp->fptr > fp->fsize) fp->fsize = fp->fptr; /* Update file size if needed */ + fp->flag |= FA__WRITTEN; /* Set file change flag */ + + LEAVE_FF(fp->fs, FR_OK); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Synchronize the File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_sync ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD tm; + BYTE *dir; + + + res = validate(fp); /* Check validity of the object */ + if (res == FR_OK) { + if (fp->flag & FA__WRITTEN) { /* Has the file been written? */ + /* Write-back dirty buffer */ +#if !_FS_TINY + if (fp->flag & FA__DIRTY) { + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1)) + LEAVE_FF(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + /* Update the directory entry */ + res = move_window(fp->fs, fp->dir_sect); + if (res == FR_OK) { + dir = fp->dir_ptr; + dir[DIR_Attr] |= AM_ARC; /* Set archive bit */ + ST_DWORD(dir+DIR_FileSize, fp->fsize); /* Update file size */ + st_clust(dir, fp->sclust); /* Update start cluster */ + tm = get_fattime(); /* Update updated time */ + ST_DWORD(dir+DIR_WrtTime, tm); + ST_WORD(dir+DIR_LstAccDate, 0); + fp->flag &= ~FA__WRITTEN; + fp->fs->wflag = 1; + res = sync_fs(fp->fs); + } + } + } + + LEAVE_FF(fp->fs, res); +} + +#endif /* !_FS_READONLY */ + + + + +/*-----------------------------------------------------------------------*/ +/* Close File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_close ( + FIL *fp /* Pointer to the file object to be closed */ +) +{ + FRESULT res; + + +#if _FS_READONLY + res = validate(fp); + { +#if _FS_REENTRANT + FATFS *fs = 0; + if (res == FR_OK) fs = fp->fs; /* Get corresponding file system object */ +#endif + if (res == FR_OK) fp->fs = 0; /* Invalidate file object */ + LEAVE_FF(fs, res); + } +#else + res = f_sync(fp); /* Flush cached data */ +#if _FS_LOCK + if (res == FR_OK) { /* Decrement open counter */ +#if _FS_REENTRANT + res = validate(fp); + if (res == FR_OK) { + res = dec_lock(fp->fs,fp->lockid); + unlock_fs(fp->fs, FR_OK); + } +#else + res = dec_lock(fp->fs,fp->lockid); +#endif + } +#endif + if (res == FR_OK) fp->fs = 0; /* Invalidate file object */ + return res; +#endif +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Current Directory or Current Drive, Get Current Directory */ +/*-----------------------------------------------------------------------*/ + +#if _FS_RPATH >= 1 +#if _VOLUMES >= 2 +FRESULT f_chdrive ( + const TCHAR* path /* Drive number */ +) +{ + int vol; + + + vol = get_ldnumber(&path); + if (vol < 0) return FR_INVALID_DRIVE; + + CurrVol = (BYTE)vol; + + return FR_OK; +} +#endif + + +FRESULT f_chdir ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const TCHAR* path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR_ dj; + DEF_NAMEBUF; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, &path, 0); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the path */ + FREE_BUF(); + if (res == FR_OK) { /* Follow completed */ + if (!dj.dir) { + dj.fs->cdir = dj.sclust; /* Start directory itself */ + } else { + if (dj.dir[DIR_Attr] & AM_DIR) /* Reached to the directory */ + dj.fs->cdir = ld_clust(dj.fs, dj.dir); + else + res = FR_NO_PATH; /* Reached but a file */ + } + } + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + + LEAVE_FF(dj.fs, res); +} + + +#if _FS_RPATH >= 2 +FRESULT f_getcwd ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + TCHAR* buff, /* Pointer to the directory path */ + UINT len /* Size of path */ +) +{ + FRESULT res; + DIR_ dj; + UINT i, n; + DWORD ccl; + TCHAR *tp; + FILINFO fno; + DEF_NAMEBUF; + + + *buff = 0; + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, (const TCHAR**)&buff, 0); /* Get current volume */ + if (res == FR_OK) { + INIT_BUF(dj); + i = len; /* Bottom of buffer (directory stack base) */ + dj.sclust = dj.fs->cdir; /* Start to follow upper directory from current directory */ + while ((ccl = dj.sclust) != 0) { /* Repeat while current directory is a sub-directory */ + res = dir_sdi(&dj, 1); /* Get parent directory */ + if (res != FR_OK) break; + res = dir_read(&dj, 0); + if (res != FR_OK) break; + dj.sclust = ld_clust(dj.fs, dj.dir); /* Goto parent directory */ + res = dir_sdi(&dj, 0); + if (res != FR_OK) break; + do { /* Find the entry links to the child directory */ + res = dir_read(&dj, 0); + if (res != FR_OK) break; + if (ccl == ld_clust(dj.fs, dj.dir)) break; /* Found the entry */ + res = dir_next(&dj, 0); + } while (res == FR_OK); + if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */ + if (res != FR_OK) break; +#if _USE_LFN + fno.lfname = buff; + fno.lfsize = i; +#endif + get_fileinfo(&dj, &fno); /* Get the directory name and push it to the buffer */ + tp = fno.fname; +#if _USE_LFN + if (*buff) tp = buff; +#endif + for (n = 0; tp[n]; n++) ; + if (i < n + 3) { + res = FR_NOT_ENOUGH_CORE; break; + } + while (n) buff[--i] = tp[--n]; + buff[--i] = '/'; + } + tp = buff; + if (res == FR_OK) { +#if _VOLUMES >= 2 + *tp++ = '0' + CurrVol; /* Put drive number */ + *tp++ = ':'; +#endif + if (i == len) { /* Root-directory */ + *tp++ = '/'; + } else { /* Sub-directroy */ + do /* Add stacked path str */ + *tp++ = buff[i++]; + while (i < len); + } + } + *tp = 0; + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} +#endif /* _FS_RPATH >= 2 */ +#endif /* _FS_RPATH >= 1 */ + + + +#if _FS_MINIMIZE <= 2 +/*-----------------------------------------------------------------------*/ +/* Seek File R/W Pointer */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_lseek ( + FIL* fp, /* Pointer to the file object */ + DWORD ofs /* File pointer from top of file */ +) +{ + FRESULT res; + + + res = validate(fp); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->err) /* Check error */ + LEAVE_FF(fp->fs, (FRESULT)fp->err); + +#if _USE_FASTSEEK + if (fp->cltbl) { /* Fast seek */ + DWORD cl, pcl, ncl, tcl, dsc, tlen, ulen, *tbl; + + if (ofs == CREATE_LINKMAP) { /* Create CLMT */ + tbl = fp->cltbl; + tlen = *tbl++; ulen = 2; /* Given table size and required table size */ + cl = fp->sclust; /* Top of the chain */ + if (cl) { + do { + /* Get a fragment */ + tcl = cl; ncl = 0; ulen += 2; /* Top, length and used items */ + do { + pcl = cl; ncl++; + cl = get_fat(fp->fs, cl); + if (cl <= 1) ABORT(fp->fs, FR_INT_ERR); + if (cl == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + } while (cl == pcl + 1); + if (ulen <= tlen) { /* Store the length and top of the fragment */ + *tbl++ = ncl; *tbl++ = tcl; + } + } while (cl < fp->fs->n_fatent); /* Repeat until end of chain */ + } + *fp->cltbl = ulen; /* Number of items used */ + if (ulen <= tlen) + *tbl = 0; /* Terminate table */ + else + res = FR_NOT_ENOUGH_CORE; /* Given table size is smaller than required */ + + } else { /* Fast seek */ + if (ofs > fp->fsize) /* Clip offset at the file size */ + ofs = fp->fsize; + fp->fptr = ofs; /* Set file pointer */ + if (ofs) { + fp->clust = clmt_clust(fp, ofs - 1); + dsc = clust2sect(fp->fs, fp->clust); + if (!dsc) ABORT(fp->fs, FR_INT_ERR); + dsc += (ofs - 1) / SS(fp->fs) & (fp->fs->csize - 1); + if (fp->fptr % SS(fp->fs) && dsc != fp->dsect) { /* Refill sector cache if needed */ +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1)) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + if (disk_read(fp->fs->drv, fp->buf, dsc, 1)) /* Load current sector */ + ABORT(fp->fs, FR_DISK_ERR); +#endif + fp->dsect = dsc; + } + } + } + } else +#endif + + /* Normal Seek */ + { + DWORD clst, bcs, nsect, ifptr; + + if (ofs > fp->fsize /* In read-only mode, clip offset with the file size */ +#if !_FS_READONLY + && !(fp->flag & FA_WRITE) +#endif + ) ofs = fp->fsize; + + ifptr = fp->fptr; + fp->fptr = nsect = 0; + if (ofs) { + bcs = (DWORD)fp->fs->csize * SS(fp->fs); /* Cluster size (byte) */ + if (ifptr > 0 && + (ofs - 1) / bcs >= (ifptr - 1) / bcs) { /* When seek to same or following cluster, */ + fp->fptr = (ifptr - 1) & ~(bcs - 1); /* start from the current cluster */ + ofs -= fp->fptr; + clst = fp->clust; + } else { /* When seek to back cluster, */ + clst = fp->sclust; /* start from the first cluster */ +#if !_FS_READONLY + if (clst == 0) { /* If no cluster chain, create a new chain */ + clst = create_chain(fp->fs, 0); + if (clst == 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->sclust = clst; + } +#endif + fp->clust = clst; + } + if (clst != 0) { + while (ofs > bcs) { /* Cluster following loop */ +#if !_FS_READONLY + if (fp->flag & FA_WRITE) { /* Check if in write mode or not */ + clst = create_chain(fp->fs, clst); /* Force stretch if in write mode */ + if (clst == 0) { /* When disk gets full, clip file size */ + ofs = bcs; break; + } + } else +#endif + clst = get_fat(fp->fs, clst); /* Follow cluster chain if not in write mode */ + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + if (clst <= 1 || clst >= fp->fs->n_fatent) ABORT(fp->fs, FR_INT_ERR); + fp->clust = clst; + fp->fptr += bcs; + ofs -= bcs; + } + fp->fptr += ofs; + if (ofs % SS(fp->fs)) { + nsect = clust2sect(fp->fs, clst); /* Current sector */ + if (!nsect) ABORT(fp->fs, FR_INT_ERR); + nsect += ofs / SS(fp->fs); + } + } + } + if (fp->fptr % SS(fp->fs) && nsect != fp->dsect) { /* Fill sector cache if needed */ +#if !_FS_TINY +#if !_FS_READONLY + if (fp->flag & FA__DIRTY) { /* Write-back dirty sector cache */ + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1)) + ABORT(fp->fs, FR_DISK_ERR); + fp->flag &= ~FA__DIRTY; + } +#endif + if (disk_read(fp->fs->drv, fp->buf, nsect, 1)) /* Fill sector cache */ + ABORT(fp->fs, FR_DISK_ERR); +#endif + fp->dsect = nsect; + } +#if !_FS_READONLY + if (fp->fptr > fp->fsize) { /* Set file change flag if the file size is extended */ + fp->fsize = fp->fptr; + fp->flag |= FA__WRITTEN; + } +#endif + } + + LEAVE_FF(fp->fs, res); +} + + + +#if _FS_MINIMIZE <= 1 +/*-----------------------------------------------------------------------*/ +/* Create a Directory Object */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_opendir ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + DIR_* dp, /* Pointer to directory object to create */ + const /*TCHAR*/char *path /* Pointer to the directory path */ +) +{ + FRESULT res; + //FATFS* fs; + DEF_NAMEBUF; + + + if (!dp) return FR_INVALID_OBJECT; + + /* Get logical drive number */ + res = find_volume(fs, /*&path,*/ 0); + if (res == FR_OK) { + dp->fs = fs; + INIT_BUF(*dp); + res = follow_path(dp, path); /* Follow the path to the directory */ + FREE_BUF(); + if (res == FR_OK) { /* Follow completed */ + if (dp->dir) { /* It is not the origin directory itself */ + if (dp->dir[DIR_Attr] & AM_DIR) /* The object is a sub directory */ + dp->sclust = ld_clust(fs, dp->dir); + else /* The object is a file */ + res = FR_NO_PATH; + } + if (res == FR_OK) { + dp->id = fs->id; + res = dir_sdi(dp, 0); /* Rewind directory */ +#if _FS_LOCK + if (res == FR_OK) { + if (dp->sclust) { + dp->lockid = inc_lock(dp, 0); /* Lock the sub directory */ + if (!dp->lockid) + res = FR_TOO_MANY_OPEN_FILES; + } else { + dp->lockid = 0; /* Root directory need not to be locked */ + } + } +#endif + } + } + if (res == FR_NO_FILE) res = FR_NO_PATH; + } + if (res != FR_OK) dp->fs = 0; /* Invalidate the directory object if function faild */ + + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Close Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_closedir ( + DIR_ *dp /* Pointer to the directory object to be closed */ +) +{ + FRESULT res; + + + res = validate(dp); +#if _FS_LOCK + if (res == FR_OK) { /* Decrement open counter */ + if (dp->lockid) + res = dec_lock(dp->fs,dp->lockid); +#if _FS_REENTRANT + unlock_fs(dp->fs, FR_OK); +#endif + } +#endif + if (res == FR_OK) dp->fs = 0; /* Invalidate directory object */ + return res; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Read Directory Entries in Sequence */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_readdir ( + DIR_* dp, /* Pointer to the open directory object */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DEF_NAMEBUF; + + + res = validate(dp); /* Check validity of the object */ + if (res == FR_OK) { + if (!fno) { + res = dir_sdi(dp, 0); /* Rewind the directory object */ + } else { + INIT_BUF2(*dp,dp->fs); + res = dir_read(dp, 0); /* Read an item */ + if (res == FR_NO_FILE) { /* Reached end of directory */ + dp->sect = 0; + res = FR_OK; + } + if (res == FR_OK) { /* A valid entry is found */ + get_fileinfo(dp, fno); /* Get the object information */ + res = dir_next(dp, 0); /* Increment index for next */ + if (res == FR_NO_FILE) { + dp->sect = 0; + res = FR_OK; + } + } + FREE_BUF(); + } + } + + LEAVE_FF(dp->fs, res); +} + + + +#if _FS_MINIMIZE == 0 +/*-----------------------------------------------------------------------*/ +/* Get File Status */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_stat ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const /*TCHAR*/char *path, /* Pointer to the file path */ + FILINFO* fno /* Pointer to file information to return */ +) +{ + FRESULT res; + DIR_ dj; + DEF_NAMEBUF; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, /*&path,*/ 0); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) { /* Follow completed */ + if (dj.dir) { /* Found an object */ + if (fno) get_fileinfo(&dj, fno); + } else { /* It is root directory */ + res = FR_INVALID_NAME; + } + } + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Get Number of Free Clusters */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getfree ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + /*const TCHAR* path,*/ /* Path name of the logical drive number */ + DWORD* nclst /* Pointer to a variable to return number of free clusters */ + /*FATFS** fatfs*/ /* Pointer to return pointer to corresponding file system object */ +) +{ + FRESULT res; + //FATFS *fs; + DWORD n, clst, sect, stat; + UINT i; + BYTE fat, *p; + + + /* Get logical drive number */ + res = find_volume(fs/*fatfs*/, /*&path,*/ 0); + //fs = *fatfs; + if (res == FR_OK) { + /* If free_clust is valid, return it without full cluster scan */ + if (fs->free_clust <= fs->n_fatent - 2) { + *nclst = fs->free_clust; + } else { + /* Get number of free clusters */ + fat = fs->fs_type; + n = 0; + if (fat == FS_FAT12) { + clst = 2; + do { + stat = get_fat(fs, clst); + if (stat == 0xFFFFFFFF) { res = FR_DISK_ERR; break; } + if (stat == 1) { res = FR_INT_ERR; break; } + if (stat == 0) n++; + } while (++clst < fs->n_fatent); + } else { + clst = fs->n_fatent; + sect = fs->fatbase; + i = 0; p = 0; + do { + if (!i) { + res = move_window(fs, sect++); + if (res != FR_OK) break; + p = fs->win; + i = SS(fs); + } + if (fat == FS_FAT16) { + if (LD_WORD(p) == 0) n++; + p += 2; i -= 2; + } else { + if ((LD_DWORD(p) & 0x0FFFFFFF) == 0) n++; + p += 4; i -= 4; + } + } while (--clst); + } + fs->free_clust = n; + fs->fsi_flag |= 1; + *nclst = n; + } + } + LEAVE_FF(fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Truncate File */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_truncate ( + FIL* fp /* Pointer to the file object */ +) +{ + FRESULT res; + DWORD ncl; + + + res = validate(fp); /* Check validity of the object */ + if (res == FR_OK) { + if (fp->err) { /* Check error */ + res = (FRESULT)fp->err; + } else { + if (!(fp->flag & FA_WRITE)) /* Check access mode */ + res = FR_DENIED; + } + } + if (res == FR_OK) { + if (fp->fsize > fp->fptr) { + fp->fsize = fp->fptr; /* Set file size to current R/W point */ + fp->flag |= FA__WRITTEN; + if (fp->fptr == 0) { /* When set file size to zero, remove entire cluster chain */ + res = remove_chain(fp->fs, fp->sclust); + fp->sclust = 0; + } else { /* When truncate a part of the file, remove remaining clusters */ + ncl = get_fat(fp->fs, fp->clust); + res = FR_OK; + if (ncl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (ncl == 1) res = FR_INT_ERR; + if (res == FR_OK && ncl < fp->fs->n_fatent) { + res = put_fat(fp->fs, fp->clust, 0x0FFFFFFF); + if (res == FR_OK) res = remove_chain(fp->fs, ncl); + } + } +#if !_FS_TINY + if (res == FR_OK && (fp->flag & FA__DIRTY)) { + if (disk_write(fp->fs->drv, fp->buf, fp->dsect, 1)) + res = FR_DISK_ERR; + else + fp->flag &= ~FA__DIRTY; + } +#endif + } + if (res != FR_OK) fp->err = (FRESULT)res; + } + + LEAVE_FF(fp->fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Delete a File or Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_unlink ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const /*TCHAR*/char *path /* Pointer to the file or directory path */ +) +{ + FRESULT res; + DIR_ dj, sdj; + BYTE *dir; + DWORD dclst; + DEF_NAMEBUF; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, /*&path,*/ 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + if (_FS_RPATH && res == FR_OK && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; /* Cannot remove dot entry */ +#if _FS_LOCK + if (res == FR_OK) res = chk_lock(&dj, 2); /* Cannot remove open file */ +#endif + if (res == FR_OK) { /* The object is accessible */ + dir = dj.dir; + if (!dir) { + res = FR_INVALID_NAME; /* Cannot remove the start directory */ + } else { + if (dir[DIR_Attr] & AM_RDO) + res = FR_DENIED; /* Cannot remove R/O object */ + } + dclst = ld_clust(dj.fs, dir); + if (res == FR_OK && (dir[DIR_Attr] & AM_DIR)) { /* Is it a sub-dir? */ + if (dclst < 2) { + res = FR_INT_ERR; + } else { + mem_cpy(&sdj, &dj, sizeof (DIR_)); /* Check if the sub-directory is empty or not */ + sdj.sclust = dclst; + res = dir_sdi(&sdj, 2); /* Exclude dot entries */ + if (res == FR_OK) { + res = dir_read(&sdj, 0); /* Read an item */ + if (res == FR_OK /* Not empty directory */ +#if _FS_RPATH + || dclst == dj.fs->cdir /* Current directory */ +#endif + ) res = FR_DENIED; + if (res == FR_NO_FILE) res = FR_OK; /* Empty */ + } + } + } + if (res == FR_OK) { + res = dir_remove(&dj); /* Remove the directory entry */ + if (res == FR_OK) { + if (dclst) /* Remove the cluster chain if exist */ + res = remove_chain(dj.fs, dclst); + if (res == FR_OK) res = sync_fs(dj.fs); + } + } + } + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create a Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_mkdir ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const /*TCHAR*/char *path /* Pointer to the directory path */ +) +{ + FRESULT res; + DIR_ dj; + BYTE *dir, n; + DWORD dsc, dcl, pcl, tm = get_fattime(); + DEF_NAMEBUF; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, /*&path,*/ 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + if (res == FR_OK) res = FR_EXIST; /* Any object with same name is already existing */ + if (_FS_RPATH && res == FR_NO_FILE && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; + if (res == FR_NO_FILE) { /* Can create a new directory */ + dcl = create_chain(dj.fs, 0); /* Allocate a cluster for the new directory table */ + res = FR_OK; + if (dcl == 0) res = FR_DENIED; /* No space to allocate a new cluster */ + if (dcl == 1) res = FR_INT_ERR; + if (dcl == 0xFFFFFFFF) res = FR_DISK_ERR; + if (res == FR_OK) /* Flush FAT */ + res = sync_window(dj.fs); + if (res == FR_OK) { /* Initialize the new directory table */ + dsc = clust2sect(dj.fs, dcl); + dir = dj.fs->win; + mem_set(dir, 0, SS(dj.fs)); + mem_set(dir+DIR_Name, ' ', 11); /* Create "." entry */ + dir[DIR_Name] = '.'; + dir[DIR_Attr] = AM_DIR; + ST_DWORD(dir+DIR_WrtTime, tm); + st_clust(dir, dcl); + mem_cpy(dir+SZ_DIR, dir, SZ_DIR); /* Create ".." entry */ + dir[SZ_DIR+1] = '.'; pcl = dj.sclust; + if (dj.fs->fs_type == FS_FAT32 && pcl == dj.fs->dirbase) + pcl = 0; + st_clust(dir+SZ_DIR, pcl); + for (n = dj.fs->csize; n; n--) { /* Write dot entries and clear following sectors */ + dj.fs->winsect = dsc++; + dj.fs->wflag = 1; + res = sync_window(dj.fs); + if (res != FR_OK) break; + mem_set(dir, 0, SS(dj.fs)); + } + } + if (res == FR_OK) res = dir_register(&dj); /* Register the object to the directoy */ + if (res != FR_OK) { + remove_chain(dj.fs, dcl); /* Could not register, remove cluster chain */ + } else { + dir = dj.dir; + dir[DIR_Attr] = AM_DIR; /* Attribute */ + ST_DWORD(dir+DIR_WrtTime, tm); /* Created time */ + st_clust(dir, dcl); /* Table start cluster */ + dj.fs->wflag = 1; + res = sync_fs(dj.fs); + } + } + FREE_BUF(); + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Attribute */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_chmod ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const /*TCHAR*/char *path, /* Pointer to the file path */ + BYTE value, /* Attribute bits */ + BYTE mask /* Attribute mask to change */ +) +{ + FRESULT res; + DIR_ dj; + BYTE *dir; + DEF_NAMEBUF; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, /*&path,*/ 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + FREE_BUF(); + if (_FS_RPATH && res == FR_OK && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; + if (res == FR_OK) { + dir = dj.dir; + if (!dir) { /* Is it a root directory? */ + res = FR_INVALID_NAME; + } else { /* File or sub directory */ + mask &= AM_RDO|AM_HID|AM_SYS|AM_ARC; /* Valid attribute mask */ + dir[DIR_Attr] = (value & mask) | (dir[DIR_Attr] & (BYTE)~mask); /* Apply attribute change */ + dj.fs->wflag = 1; + res = sync_fs(dj.fs); + } + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Change Timestamp */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_utime ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const /*TCHAR*/char *path, /* Pointer to the file/directory name */ + const FILINFO* fno /* Pointer to the time stamp to be set */ +) +{ + FRESULT res; + DIR_ dj; + BYTE *dir; + DEF_NAMEBUF; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, /*&path,*/ 1); + if (res == FR_OK) { + INIT_BUF(dj); + res = follow_path(&dj, path); /* Follow the file path */ + FREE_BUF(); + if (_FS_RPATH && res == FR_OK && (dj.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; + if (res == FR_OK) { + dir = dj.dir; + if (!dir) { /* Root directory */ + res = FR_INVALID_NAME; + } else { /* File or sub-directory */ + ST_WORD(dir+DIR_WrtTime, fno->ftime); + ST_WORD(dir+DIR_WrtDate, fno->fdate); + dj.fs->wflag = 1; + res = sync_fs(dj.fs); + } + } + } + + LEAVE_FF(dj.fs, res); +} + + + + +/*-----------------------------------------------------------------------*/ +/* Rename File/Directory */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_rename ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const /*TCHAR*/char *path_old, /* Pointer to the old name */ + const /*TCHAR*/char *path_new /* Pointer to the new name */ +) +{ + FRESULT res; + DIR_ djo, djn; + BYTE buf[21], *dir; + DWORD dw; + DEF_NAMEBUF; + + + /* Get logical drive number of the source object */ + djo.fs=fs; + res = find_volume(djo.fs, /*&path_old,*/ 1); + if (res == FR_OK) { + djn.fs = djo.fs; + INIT_BUF(djo); + res = follow_path(&djo, path_old); /* Check old object */ + if (_FS_RPATH && res == FR_OK && (djo.fn[NS] & NS_DOT)) + res = FR_INVALID_NAME; +#if _FS_LOCK + if (res == FR_OK) res = chk_lock(&djo, 2); +#endif + if (res == FR_OK) { /* Old object is found */ + if (!djo.dir) { /* Is root dir? */ + res = FR_NO_FILE; + } else { + mem_cpy(buf, djo.dir+DIR_Attr, 21); /* Save the object information except for name */ + mem_cpy(&djn, &djo, sizeof (DIR_)); /* Check new object */ + res = follow_path(&djn, path_new); + if (res == FR_OK) res = FR_EXIST; /* The new object name is already existing */ + if (res == FR_NO_FILE) { /* Is it a valid path and no name collision? */ +/* Start critical section that any interruption can cause a cross-link */ + res = dir_register(&djn); /* Register the new entry */ + if (res == FR_OK) { + dir = djn.dir; /* Copy object information except for name */ + mem_cpy(dir+13, buf+2, 19); + dir[DIR_Attr] = buf[0] | AM_ARC; + djo.fs->wflag = 1; + if (djo.sclust != djn.sclust && (dir[DIR_Attr] & AM_DIR)) { /* Update .. entry in the directory if needed */ + dw = clust2sect(djo.fs, ld_clust(djo.fs, dir)); + if (!dw) { + res = FR_INT_ERR; + } else { + res = move_window(djo.fs, dw); + dir = djo.fs->win+SZ_DIR; /* .. entry */ + if (res == FR_OK && dir[1] == '.') { + dw = (djo.fs->fs_type == FS_FAT32 && djn.sclust == djo.fs->dirbase) ? 0 : djn.sclust; + st_clust(dir, dw); + djo.fs->wflag = 1; + } + } + } + if (res == FR_OK) { + res = dir_remove(&djo); /* Remove old entry */ + if (res == FR_OK) + res = sync_fs(djo.fs); + } + } +/* End critical section */ + } + } + } + FREE_BUF(); + } + + LEAVE_FF(djo.fs, res); +} + +#endif /* !_FS_READONLY */ +#endif /* _FS_MINIMIZE == 0 */ +#endif /* _FS_MINIMIZE <= 1 */ +#endif /* _FS_MINIMIZE <= 2 */ + + + +#if _USE_LABEL +/*-----------------------------------------------------------------------*/ +/* Get volume label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_getlabel ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const TCHAR* path, /* Path name of the logical drive number */ + TCHAR* label, /* Pointer to a buffer to return the volume label */ + DWORD* sn /* Pointer to a variable to return the volume serial number */ +) +{ + FRESULT res; + DIR_ dj; + UINT i, j; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, &path, 0); + + /* Get volume label */ + if (res == FR_OK && label) { + dj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = dir_read(&dj, 1); /* Get an entry with AM_VOL */ + if (res == FR_OK) { /* A volume label is exist */ +#if _LFN_UNICODE + WCHAR w; + i = j = 0; + do { + w = (i < 11) ? dj.dir[i++] : ' '; + if (IsDBCS1(w) && i < 11 && IsDBCS2(dj.dir[i])) + w = w << 8 | dj.dir[i++]; + label[j++] = ff_convert(w, 1); /* OEM -> Unicode */ + } while (j < 11); +#else + mem_cpy(label, dj.dir, 11); +#endif + j = 11; + do { + label[j] = 0; + if (!j) break; + } while (label[--j] == ' '); + } + if (res == FR_NO_FILE) { /* No label, return nul string */ + label[0] = 0; + res = FR_OK; + } + } + } + + /* Get volume serial number */ + if (res == FR_OK && sn) { + res = move_window(dj.fs, dj.fs->volbase); + if (res == FR_OK) { + i = dj.fs->fs_type == FS_FAT32 ? BS_VolID32 : BS_VolID; + *sn = LD_DWORD(&dj.fs->win[i]); + } + } + + LEAVE_FF(dj.fs, res); +} + + + +#if !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Set volume label */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_setlabel ( + FATFS *fs, /* By TFT: added to get rid of static variables */ + const TCHAR* label /* Pointer to the volume label to set */ +) +{ + FRESULT res; + DIR_ dj; + BYTE vn[11]; + UINT i, j, sl; + WCHAR w; + DWORD tm; + + + /* Get logical drive number */ + dj.fs=fs; + res = find_volume(dj.fs, &label, 1); + if (res) LEAVE_FF(dj.fs, res); + + /* Create a volume label in directory form */ + vn[0] = 0; + for (sl = 0; label[sl]; sl++) ; /* Get name length */ + for ( ; sl && label[sl-1] == ' '; sl--) ; /* Remove trailing spaces */ + if (sl) { /* Create volume label in directory form */ + i = j = 0; + do { +#if _LFN_UNICODE + w = ff_convert(ff_wtoupper(label[i++]), 0); +#else + w = (BYTE)label[i++]; + if (IsDBCS1(w)) + w = (j < 10 && i < sl && IsDBCS2(label[i])) ? w << 8 | (BYTE)label[i++] : 0; +#if _USE_LFN + w = ff_convert(ff_wtoupper(ff_convert(w, 1)), 0); +#else + if (IsLower(w)) w -= 0x20; /* To upper ASCII characters */ +#ifdef _EXCVT + if (w >= 0x80) w = ExCvt[w - 0x80]; /* To upper extended characters (SBCS cfg) */ +#else + if (!_DF1S && w >= 0x80) w = 0; /* Reject extended characters (ASCII cfg) */ +#endif +#endif +#endif + if (!w || chk_chr("\"*+,.:;<=>\?[]|\x7F", w) || j >= (UINT)((w >= 0x100) ? 10 : 11)) /* Reject invalid characters for volume label */ + LEAVE_FF(dj.fs, FR_INVALID_NAME); + if (w >= 0x100) vn[j++] = (BYTE)(w >> 8); + vn[j++] = (BYTE)w; + } while (i < sl); + while (j < 11) vn[j++] = ' '; + } + + /* Set volume label */ + dj.sclust = 0; /* Open root directory */ + res = dir_sdi(&dj, 0); + if (res == FR_OK) { + res = dir_read(&dj, 1); /* Get an entry with AM_VOL */ + if (res == FR_OK) { /* A volume label is found */ + if (vn[0]) { + mem_cpy(dj.dir, vn, 11); /* Change the volume label name */ + tm = get_fattime(); + ST_DWORD(dj.dir+DIR_WrtTime, tm); + } else { + dj.dir[0] = DDE; /* Remove the volume label */ + } + dj.fs->wflag = 1; + res = sync_fs(dj.fs); + } else { /* No volume label is found or error */ + if (res == FR_NO_FILE) { + res = FR_OK; + if (vn[0]) { /* Create volume label as new */ + res = dir_alloc(&dj, 1); /* Allocate an entry for volume label */ + if (res == FR_OK) { + mem_set(dj.dir, 0, SZ_DIR); /* Set volume label */ + mem_cpy(dj.dir, vn, 11); + dj.dir[DIR_Attr] = AM_VOL; + tm = get_fattime(); + ST_DWORD(dj.dir+DIR_WrtTime, tm); + dj.fs->wflag = 1; + res = sync_fs(dj.fs); + } + } + } + } + } + + LEAVE_FF(dj.fs, res); +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_LABEL */ + + + +/*-----------------------------------------------------------------------*/ +/* Forward data to the stream directly (available on only tiny cfg) */ +/*-----------------------------------------------------------------------*/ +#if _USE_FORWARD && _FS_TINY + +FRESULT f_forward ( + FIL* fp, /* Pointer to the file object */ + UINT (*func)(const BYTE*,UINT), /* Pointer to the streaming function */ + UINT btf, /* Number of bytes to forward */ + UINT* bf /* Pointer to number of bytes forwarded */ +) +{ + FRESULT res; + DWORD remain, clst, sect; + UINT rcnt; + BYTE csect; + + + *bf = 0; /* Clear transfer byte counter */ + + res = validate(fp); /* Check validity of the object */ + if (res != FR_OK) LEAVE_FF(fp->fs, res); + if (fp->err) /* Check error */ + LEAVE_FF(fp->fs, (FRESULT)fp->err); + if (!(fp->flag & FA_READ)) /* Check access mode */ + LEAVE_FF(fp->fs, FR_DENIED); + + remain = fp->fsize - fp->fptr; + if (btf > remain) btf = (UINT)remain; /* Truncate btf by remaining bytes */ + + for ( ; btf && (*func)(0, 0); /* Repeat until all data transferred or stream becomes busy */ + fp->fptr += rcnt, *bf += rcnt, btf -= rcnt) { + csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* Sector offset in the cluster */ + if ((fp->fptr % SS(fp->fs)) == 0) { /* On the sector boundary? */ + if (!csect) { /* On the cluster boundary? */ + clst = (fp->fptr == 0) ? /* On the top of the file? */ + fp->sclust : get_fat(fp->fs, fp->clust); + if (clst <= 1) ABORT(fp->fs, FR_INT_ERR); + if (clst == 0xFFFFFFFF) ABORT(fp->fs, FR_DISK_ERR); + fp->clust = clst; /* Update current cluster */ + } + } + sect = clust2sect(fp->fs, fp->clust); /* Get current data sector */ + if (!sect) ABORT(fp->fs, FR_INT_ERR); + sect += csect; + if (move_window(fp->fs, sect)) /* Move sector window */ + ABORT(fp->fs, FR_DISK_ERR); + fp->dsect = sect; + rcnt = SS(fp->fs) - (WORD)(fp->fptr % SS(fp->fs)); /* Forward data from sector window */ + if (rcnt > btf) rcnt = btf; + rcnt = (*func)(&fp->fs->win[(WORD)fp->fptr % SS(fp->fs)], rcnt); + if (!rcnt) ABORT(fp->fs, FR_INT_ERR); + } + + LEAVE_FF(fp->fs, FR_OK); +} +#endif /* _USE_FORWARD */ + + + +#if _USE_MKFS && !_FS_READONLY +/*-----------------------------------------------------------------------*/ +/* Create File System on the Drive */ +/*-----------------------------------------------------------------------*/ +#define N_ROOTDIR 512 /* Number of root directory entries for FAT12/16 */ +#define N_FATS 1 /* Number of FAT copies (1 or 2) */ + + +FRESULT f_mkfs ( + const TCHAR* path, /* Logical drive number */ + BYTE sfd, /* Partitioning rule 0:FDISK, 1:SFD */ + UINT au /* Allocation unit [bytes] */ +) +{ + static const WORD vst[] = { 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 0}; + static const WORD cst[] = {32768, 16384, 8192, 4096, 2048, 16384, 8192, 4096, 2048, 1024, 512}; + int vol; + BYTE fmt, md, sys, *tbl, pdrv, part; + DWORD n_clst, vs, n, wsect; + UINT i; + DWORD b_vol, b_fat, b_dir, b_data; /* LBA */ + DWORD n_vol, n_rsv, n_fat, n_dir; /* Size */ + FATFS *fs; + DSTATUS stat; + + + /* Check mounted drive and clear work area */ + vol = get_ldnumber(&path); + if (vol < 0) return FR_INVALID_DRIVE; + if (sfd > 1) return FR_INVALID_PARAMETER; + if (au & (au - 1)) return FR_INVALID_PARAMETER; + fs = FatFs[vol]; + if (!fs) return FR_NOT_ENABLED; + fs->fs_type = 0; + pdrv = LD2PD(vol); /* Physical drive */ + part = LD2PT(vol); /* Partition (0:auto detect, 1-4:get from partition table)*/ + + /* Get disk statics */ + stat = RES_OK;//disk_initialize(pdrv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; +#if _MAX_SS != 512 /* Get disk sector size */ + if (disk_ioctl(pdrv, GET_SECTOR_SIZE, &SS(fs)) != RES_OK || SS(fs) > _MAX_SS) + return FR_DISK_ERR; +#endif + if (_MULTI_PARTITION && part) { + /* Get partition information from partition table in the MBR */ + if (disk_read(pdrv, fs->win, 0, 1)) return FR_DISK_ERR; + if (LD_WORD(fs->win+BS_55AA) != 0xAA55) return FR_MKFS_ABORTED; + tbl = &fs->win[MBR_Table + (part - 1) * SZ_PTE]; + if (!tbl[4]) return FR_MKFS_ABORTED; /* No partition? */ + b_vol = LD_DWORD(tbl+8); /* Volume start sector */ + n_vol = LD_DWORD(tbl+12); /* Volume size */ + } else { + /* Create a partition in this function */ + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &n_vol) != RES_OK || n_vol < 128) + return FR_DISK_ERR; + b_vol = (sfd) ? 0 : 63; /* Volume start sector */ + n_vol -= b_vol; /* Volume size */ + } + + if (!au) { /* AU auto selection */ + vs = n_vol / (2000 / (SS(fs) / 512)); + for (i = 0; vs < vst[i]; i++) ; + au = cst[i]; + } + au /= SS(fs); /* Number of sectors per cluster */ + if (au == 0) au = 1; + if (au > 128) au = 128; + + /* Pre-compute number of clusters and FAT sub-type */ + n_clst = n_vol / au; + fmt = FS_FAT12; + if (n_clst >= MIN_FAT16) fmt = FS_FAT16; + if (n_clst >= MIN_FAT32) fmt = FS_FAT32; + + /* Determine offset and size of FAT structure */ + if (fmt == FS_FAT32) { + n_fat = ((n_clst * 4) + 8 + SS(fs) - 1) / SS(fs); + n_rsv = 32; + n_dir = 0; + } else { + n_fat = (fmt == FS_FAT12) ? (n_clst * 3 + 1) / 2 + 3 : (n_clst * 2) + 4; + n_fat = (n_fat + SS(fs) - 1) / SS(fs); + n_rsv = 1; + n_dir = (DWORD)N_ROOTDIR * SZ_DIR / SS(fs); + } + b_fat = b_vol + n_rsv; /* FAT area start sector */ + b_dir = b_fat + n_fat * N_FATS; /* Directory area start sector */ + b_data = b_dir + n_dir; /* Data area start sector */ + if (n_vol < b_data + au - b_vol) return FR_MKFS_ABORTED; /* Too small volume */ + + /* Align data start sector to erase block boundary (for flash memory media) */ + if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &n) != RES_OK || !n || n > 32768) n = 1; + n = (b_data + n - 1) & ~(n - 1); /* Next nearest erase block from current data start */ + n = (n - b_data) / N_FATS; + if (fmt == FS_FAT32) { /* FAT32: Move FAT offset */ + n_rsv += n; + b_fat += n; + } else { /* FAT12/16: Expand FAT size */ + n_fat += n; + } + + /* Determine number of clusters and final check of validity of the FAT sub-type */ + n_clst = (n_vol - n_rsv - n_fat * N_FATS - n_dir) / au; + if ( (fmt == FS_FAT16 && n_clst < MIN_FAT16) + || (fmt == FS_FAT32 && n_clst < MIN_FAT32)) + return FR_MKFS_ABORTED; + + /* Determine system ID in the partition table */ + if (fmt == FS_FAT32) { + sys = 0x0C; /* FAT32X */ + } else { + if (fmt == FS_FAT12 && n_vol < 0x10000) { + sys = 0x01; /* FAT12(<65536) */ + } else { + sys = (n_vol < 0x10000) ? 0x04 : 0x06; /* FAT16(<65536) : FAT12/16(>=65536) */ + } + } + + if (_MULTI_PARTITION && part) { + /* Update system ID in the partition table */ + tbl = &fs->win[MBR_Table + (part - 1) * SZ_PTE]; + tbl[4] = sys; + if (disk_write(pdrv, fs->win, 0, 1)) /* Write it to teh MBR */ + return FR_DISK_ERR; + md = 0xF8; + } else { + if (sfd) { /* No partition table (SFD) */ + md = 0xF0; + } else { /* Create partition table (FDISK) */ + mem_set(fs->win, 0, SS(fs)); + tbl = fs->win+MBR_Table; /* Create partition table for single partition in the drive */ + tbl[1] = 1; /* Partition start head */ + tbl[2] = 1; /* Partition start sector */ + tbl[3] = 0; /* Partition start cylinder */ + tbl[4] = sys; /* System type */ + tbl[5] = 254; /* Partition end head */ + n = (b_vol + n_vol) / 63 / 255; + tbl[6] = (BYTE)(n >> 2 | 63); /* Partition end sector */ + tbl[7] = (BYTE)n; /* End cylinder */ + ST_DWORD(tbl+8, 63); /* Partition start in LBA */ + ST_DWORD(tbl+12, n_vol); /* Partition size in LBA */ + ST_WORD(fs->win+BS_55AA, 0xAA55); /* MBR signature */ + if (disk_write(pdrv, fs->win, 0, 1)) /* Write it to the MBR */ + return FR_DISK_ERR; + md = 0xF8; + } + } + + /* Create BPB in the VBR */ + tbl = fs->win; /* Clear sector */ + mem_set(tbl, 0, SS(fs)); + mem_cpy(tbl, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code, OEM name */ + i = SS(fs); /* Sector size */ + ST_WORD(tbl+BPB_BytsPerSec, i); + tbl[BPB_SecPerClus] = (BYTE)au; /* Sectors per cluster */ + ST_WORD(tbl+BPB_RsvdSecCnt, n_rsv); /* Reserved sectors */ + tbl[BPB_NumFATs] = N_FATS; /* Number of FATs */ + i = (fmt == FS_FAT32) ? 0 : N_ROOTDIR; /* Number of root directory entries */ + ST_WORD(tbl+BPB_RootEntCnt, i); + if (n_vol < 0x10000) { /* Number of total sectors */ + ST_WORD(tbl+BPB_TotSec16, n_vol); + } else { + ST_DWORD(tbl+BPB_TotSec32, n_vol); + } + tbl[BPB_Media] = md; /* Media descriptor */ + ST_WORD(tbl+BPB_SecPerTrk, 63); /* Number of sectors per track */ + ST_WORD(tbl+BPB_NumHeads, 255); /* Number of heads */ + ST_DWORD(tbl+BPB_HiddSec, b_vol); /* Hidden sectors */ + n = get_fattime(); /* Use current time as VSN */ + if (fmt == FS_FAT32) { + ST_DWORD(tbl+BS_VolID32, n); /* VSN */ + ST_DWORD(tbl+BPB_FATSz32, n_fat); /* Number of sectors per FAT */ + ST_DWORD(tbl+BPB_RootClus, 2); /* Root directory start cluster (2) */ + ST_WORD(tbl+BPB_FSInfo, 1); /* FSINFO record offset (VBR+1) */ + ST_WORD(tbl+BPB_BkBootSec, 6); /* Backup boot record offset (VBR+6) */ + tbl[BS_DrvNum32] = 0x80; /* Drive number */ + tbl[BS_BootSig32] = 0x29; /* Extended boot signature */ + mem_cpy(tbl+BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ + } else { + ST_DWORD(tbl+BS_VolID, n); /* VSN */ + ST_WORD(tbl+BPB_FATSz16, n_fat); /* Number of sectors per FAT */ + tbl[BS_DrvNum] = 0x80; /* Drive number */ + tbl[BS_BootSig] = 0x29; /* Extended boot signature */ + mem_cpy(tbl+BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ + } + ST_WORD(tbl+BS_55AA, 0xAA55); /* Signature (Offset is fixed here regardless of sector size) */ + if (disk_write(pdrv, tbl, b_vol, 1)) /* Write it to the VBR sector */ + return FR_DISK_ERR; + if (fmt == FS_FAT32) /* Write backup VBR if needed (VBR+6) */ + disk_write(pdrv, tbl, b_vol + 6, 1); + + /* Initialize FAT area */ + wsect = b_fat; + for (i = 0; i < N_FATS; i++) { /* Initialize each FAT copy */ + mem_set(tbl, 0, SS(fs)); /* 1st sector of the FAT */ + n = md; /* Media descriptor byte */ + if (fmt != FS_FAT32) { + n |= (fmt == FS_FAT12) ? 0x00FFFF00 : 0xFFFFFF00; + ST_DWORD(tbl+0, n); /* Reserve cluster #0-1 (FAT12/16) */ + } else { + n |= 0xFFFFFF00; + ST_DWORD(tbl+0, n); /* Reserve cluster #0-1 (FAT32) */ + ST_DWORD(tbl+4, 0xFFFFFFFF); + ST_DWORD(tbl+8, 0x0FFFFFFF); /* Reserve cluster #2 for root directory */ + } + if (disk_write(pdrv, tbl, wsect++, 1)) + return FR_DISK_ERR; + mem_set(tbl, 0, SS(fs)); /* Fill following FAT entries with zero */ + for (n = 1; n < n_fat; n++) { /* This loop may take a time on FAT32 volume due to many single sector writes */ + if (disk_write(pdrv, tbl, wsect++, 1)) + return FR_DISK_ERR; + } + } + + /* Initialize root directory */ + i = (fmt == FS_FAT32) ? au : n_dir; + do { + if (disk_write(pdrv, tbl, wsect++, 1)) + return FR_DISK_ERR; + } while (--i); + +#if _USE_ERASE /* Erase data area if needed */ + { + DWORD eb[2]; + + eb[0] = wsect; eb[1] = wsect + (n_clst - ((fmt == FS_FAT32) ? 1 : 0)) * au - 1; + disk_ioctl(pdrv, CTRL_ERASE_SECTOR, eb); + } +#endif + + /* Create FSINFO if needed */ + if (fmt == FS_FAT32) { + ST_DWORD(tbl+FSI_LeadSig, 0x41615252); + ST_DWORD(tbl+FSI_StrucSig, 0x61417272); + ST_DWORD(tbl+FSI_Free_Count, n_clst - 1); /* Number of free clusters */ + ST_DWORD(tbl+FSI_Nxt_Free, 2); /* Last allocated cluster# */ + ST_WORD(tbl+BS_55AA, 0xAA55); + disk_write(pdrv, tbl, b_vol + 1, 1); /* Write original (VBR+1) */ + disk_write(pdrv, tbl, b_vol + 7, 1); /* Write backup (VBR+7) */ + } + + return (disk_ioctl(pdrv, CTRL_SYNC, 0) == RES_OK) ? FR_OK : FR_DISK_ERR; +} + + + +#if _MULTI_PARTITION +/*-----------------------------------------------------------------------*/ +/* Divide Physical Drive */ +/*-----------------------------------------------------------------------*/ + +FRESULT f_fdisk ( + BYTE pdrv, /* Physical drive number */ + const DWORD szt[], /* Pointer to the size table for each partitions */ + void* work /* Pointer to the working buffer */ +) +{ + UINT i, n, sz_cyl, tot_cyl, b_cyl, e_cyl, p_cyl; + BYTE s_hd, e_hd, *p, *buf = (BYTE*)work; + DSTATUS stat; + DWORD sz_disk, sz_part, s_part; + + + stat = RES_OK;//disk_initialize(pdrv); + if (stat & STA_NOINIT) return FR_NOT_READY; + if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; + if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_disk)) return FR_DISK_ERR; + + /* Determine CHS in the table regardless of the drive geometry */ + for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2) ; + if (n == 256) n--; + e_hd = n - 1; + sz_cyl = 63 * n; + tot_cyl = sz_disk / sz_cyl; + + /* Create partition table */ + mem_set(buf, 0, _MAX_SS); + p = buf + MBR_Table; b_cyl = 0; + for (i = 0; i < 4; i++, p += SZ_PTE) { + p_cyl = (szt[i] <= 100U) ? (DWORD)tot_cyl * szt[i] / 100 : szt[i] / sz_cyl; + if (!p_cyl) continue; + s_part = (DWORD)sz_cyl * b_cyl; + sz_part = (DWORD)sz_cyl * p_cyl; + if (i == 0) { /* Exclude first track of cylinder 0 */ + s_hd = 1; + s_part += 63; sz_part -= 63; + } else { + s_hd = 0; + } + e_cyl = b_cyl + p_cyl - 1; + if (e_cyl >= tot_cyl) return FR_INVALID_PARAMETER; + + /* Set partition table */ + p[1] = s_hd; /* Start head */ + p[2] = (BYTE)((b_cyl >> 2) + 1); /* Start sector */ + p[3] = (BYTE)b_cyl; /* Start cylinder */ + p[4] = 0x06; /* System type (temporary setting) */ + p[5] = e_hd; /* End head */ + p[6] = (BYTE)((e_cyl >> 2) + 63); /* End sector */ + p[7] = (BYTE)e_cyl; /* End cylinder */ + ST_DWORD(p + 8, s_part); /* Start sector in LBA */ + ST_DWORD(p + 12, sz_part); /* Partition size */ + + /* Next partition */ + b_cyl += p_cyl; + } + ST_WORD(p, 0xAA55); + + /* Write it to the MBR */ + return (disk_write(pdrv, buf, 0, 1) || disk_ioctl(pdrv, CTRL_SYNC, 0)) ? FR_DISK_ERR : FR_OK; +} + + +#endif /* _MULTI_PARTITION */ +#endif /* _USE_MKFS && !_FS_READONLY */ + + + + +#if _USE_STRFUNC +/*-----------------------------------------------------------------------*/ +/* Get a string from the file */ +/*-----------------------------------------------------------------------*/ + +TCHAR* f_gets ( + TCHAR* buff, /* Pointer to the string buffer to read */ + int len, /* Size of string buffer (characters) */ + FIL* fp /* Pointer to the file object */ +) +{ + int n = 0; + TCHAR c, *p = buff; + BYTE s[2]; + UINT rc; + + + while (n < len - 1) { /* Read characters until buffer gets filled */ +#if _LFN_UNICODE +#if _STRF_ENCODE == 3 /* Read a character in UTF-8 */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = s[0]; + if (c >= 0x80) { + if (c < 0xC0) continue; /* Skip stray trailer */ + if (c < 0xE0) { /* Two-byte sequence */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = (c & 0x1F) << 6 | (s[0] & 0x3F); + if (c < 0x80) c = '?'; + } else { + if (c < 0xF0) { /* Three-byte sequence */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + c = c << 12 | (s[0] & 0x3F) << 6 | (s[1] & 0x3F); + if (c < 0x800) c = '?'; + } else { /* Reject four-byte sequence */ + c = '?'; + } + } + } +#elif _STRF_ENCODE == 2 /* Read a character in UTF-16BE */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + c = s[1] + (s[0] << 8); +#elif _STRF_ENCODE == 1 /* Read a character in UTF-16LE */ + f_read(fp, s, 2, &rc); + if (rc != 2) break; + c = s[0] + (s[1] << 8); +#else /* Read a character in ANSI/OEM */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = s[0]; + if (IsDBCS1(c)) { + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = (c << 8) + s[0]; + } + c = ff_convert(c, 1); /* OEM -> Unicode */ + if (!c) c = '?'; +#endif +#else /* Read a character without conversion */ + f_read(fp, s, 1, &rc); + if (rc != 1) break; + c = s[0]; +#endif + if (_USE_STRFUNC == 2 && c == '\r') continue; /* Strip '\r' */ + *p++ = c; + n++; + if (c == '\n') break; /* Break on EOL */ + } + *p = 0; + return n ? buff : 0; /* When no data read (eof or error), return with error. */ +} + + + +#if !_FS_READONLY +#include +/*-----------------------------------------------------------------------*/ +/* Put a character to the file */ +/*-----------------------------------------------------------------------*/ + +typedef struct { + FIL* fp; + int idx, nchr; + BYTE buf[64]; +} putbuff; + + +static +void putc_bfd ( + putbuff* pb, + TCHAR c +) +{ + UINT bw; + int i; + + + if (_USE_STRFUNC == 2 && c == '\n') /* LF -> CRLF conversion */ + putc_bfd(pb, '\r'); + + i = pb->idx; /* Buffer write index (-1:error) */ + if (i < 0) return; + +#if _LFN_UNICODE +#if _STRF_ENCODE == 3 /* Write a character in UTF-8 */ + if (c < 0x80) { /* 7-bit */ + pb->buf[i++] = (BYTE)c; + } else { + if (c < 0x800) { /* 11-bit */ + pb->buf[i++] = (BYTE)(0xC0 | c >> 6); + } else { /* 16-bit */ + pb->buf[i++] = (BYTE)(0xE0 | c >> 12); + pb->buf[i++] = (BYTE)(0x80 | (c >> 6 & 0x3F)); + } + pb->buf[i++] = (BYTE)(0x80 | (c & 0x3F)); + } +#elif _STRF_ENCODE == 2 /* Write a character in UTF-16BE */ + pb->buf[i++] = (BYTE)(c >> 8); + pb->buf[i++] = (BYTE)c; +#elif _STRF_ENCODE == 1 /* Write a character in UTF-16LE */ + pb->buf[i++] = (BYTE)c; + pb->buf[i++] = (BYTE)(c >> 8); +#else /* Write a character in ANSI/OEM */ + c = ff_convert(c, 0); /* Unicode -> OEM */ + if (!c) c = '?'; + if (c >= 0x100) + pb->buf[i++] = (BYTE)(c >> 8); + pb->buf[i++] = (BYTE)c; +#endif +#else /* Write a character without conversion */ + pb->buf[i++] = (BYTE)c; +#endif + + if (i >= (int)(sizeof pb->buf) - 3) { /* Write buffered characters to the file */ + f_write(pb->fp, pb->buf, (UINT)i, &bw); + i = (bw == (UINT)i) ? 0 : -1; + } + pb->idx = i; + pb->nchr++; +} + + + +int f_putc ( + TCHAR c, /* A character to be output */ + FIL* fp /* Pointer to the file object */ +) +{ + putbuff pb; + UINT nw; + + + pb.fp = fp; /* Initialize output buffer */ + pb.nchr = pb.idx = 0; + + putc_bfd(&pb, c); /* Put a character */ + + if ( pb.idx >= 0 /* Flush buffered characters to the file */ + && f_write(pb.fp, pb.buf, (UINT)pb.idx, &nw) == FR_OK + && (UINT)pb.idx == nw) return pb.nchr; + return EOF; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a string to the file */ +/*-----------------------------------------------------------------------*/ + +int f_puts ( + const TCHAR* str, /* Pointer to the string to be output */ + FIL* fp /* Pointer to the file object */ +) +{ + putbuff pb; + UINT nw; + + + pb.fp = fp; /* Initialize output buffer */ + pb.nchr = pb.idx = 0; + + while (*str) /* Put the string */ + putc_bfd(&pb, *str++); + + if ( pb.idx >= 0 /* Flush buffered characters to the file */ + && f_write(pb.fp, pb.buf, (UINT)pb.idx, &nw) == FR_OK + && (UINT)pb.idx == nw) return pb.nchr; + return EOF; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Put a formatted string to the file */ +/*-----------------------------------------------------------------------*/ + +int f_printf ( + FIL* fp, /* Pointer to the file object */ + const TCHAR* fmt, /* Pointer to the format string */ + ... /* Optional arguments... */ +) +{ + va_list arp; + BYTE f, r; + UINT nw, i, j, w; + DWORD v; + TCHAR c, d, s[16], *p; + putbuff pb; + + + pb.fp = fp; /* Initialize output buffer */ + pb.nchr = pb.idx = 0; + + va_start(arp, fmt); + + for (;;) { + c = *fmt++; + if (c == 0) break; /* End of string */ + if (c != '%') { /* Non escape character */ + putc_bfd(&pb, c); + continue; + } + w = f = 0; + c = *fmt++; + if (c == '0') { /* Flag: '0' padding */ + f = 1; c = *fmt++; + } else { + if (c == '-') { /* Flag: left justified */ + f = 2; c = *fmt++; + } + } + while (IsDigit(c)) { /* Precision */ + w = w * 10 + c - '0'; + c = *fmt++; + } + if (c == 'l' || c == 'L') { /* Prefix: Size is long int */ + f |= 4; c = *fmt++; + } + if (!c) break; + d = c; + if (IsLower(d)) d -= 0x20; + switch (d) { /* Type is... */ + case 'S' : /* String */ + p = va_arg(arp, TCHAR*); + for (j = 0; p[j]; j++) ; + if (!(f & 2)) { + while (j++ < w) putc_bfd(&pb, ' '); + } + while (*p) putc_bfd(&pb, *p++); + while (j++ < w) putc_bfd(&pb, ' '); + continue; + case 'C' : /* Character */ + putc_bfd(&pb, (TCHAR)va_arg(arp, int)); continue; + case 'B' : /* Binary */ + r = 2; break; + case 'O' : /* Octal */ + r = 8; break; + case 'D' : /* Signed decimal */ + case 'U' : /* Unsigned decimal */ + r = 10; break; + case 'X' : /* Hexdecimal */ + r = 16; break; + default: /* Unknown type (pass-through) */ + putc_bfd(&pb, c); continue; + } + + /* Get an argument and put it in numeral */ + v = (f & 4) ? (DWORD)va_arg(arp, long) : ((d == 'D') ? (DWORD)(long)va_arg(arp, int) : (DWORD)va_arg(arp, unsigned int)); + if (d == 'D' && (v & 0x80000000)) { + v = 0 - v; + f |= 8; + } + i = 0; + do { + d = (TCHAR)(v % r); v /= r; + if (d > 9) d += (c == 'x') ? 0x27 : 0x07; + s[i++] = d + '0'; + } while (v && i < sizeof s / sizeof s[0]); + if (f & 8) s[i++] = '-'; + j = i; d = (f & 1) ? '0' : ' '; + while (!(f & 2) && j++ < w) putc_bfd(&pb, d); + do putc_bfd(&pb, s[--i]); while (i); + while (j++ < w) putc_bfd(&pb, d); + } + + va_end(arp); + + if ( pb.idx >= 0 /* Flush buffered characters to the file */ + && f_write(pb.fp, pb.buf, (UINT)pb.idx, &nw) == FR_OK + && (UINT)pb.idx == nw) return pb.nchr; + return EOF; +} + +#endif /* !_FS_READONLY */ +#endif /* _USE_STRFUNC */ + +#endif //WITH_FILESYSTEM diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/ff.h b/lib/miosix-kernel/miosix/filesystem/fat32/ff.h new file mode 100644 index 00000000..42d9e3ae --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/ff.h @@ -0,0 +1,385 @@ +/*---------------------------------------------------------------------------/ +/ FatFs - FAT file system module include file R0.10 (C)ChaN, 2013 +/----------------------------------------------------------------------------/ +/ FatFs module is a generic FAT file system module for small embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2013, ChaN, all right reserved. +/ +/ * The FatFs module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial product UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/----------------------------------------------------------------------------*/ + +/* + * This version of FatFs has been modified to adapt it to the requirements of + * Miosix: + * - C++: moved from C to C++ to allow calling other Miosix code + * - utf8: the original FatFs API supported only utf16 for file names, but the + * Miosix filesystem API has to be utf8 (aka, according with the + * "utf8 everywhere mainfesto", doesn't want to deal with that crap). + * For efficiency reasons the unicode conversion is done inside the FatFs code + * - removal of global variables: to allow to create an arbitrary number of + * independent Fat32 filesystems + * - unixification: removal of the dos-like drive numbering scheme and + * addition of an inode field in the FILINFO struct + */ + +#ifndef _FATFS +#define _FATFS 80960 /* Revision ID */ + +//#ifdef __cplusplus +//extern "C" { +//#endif + +#include +#include "config/miosix_settings.h" + +#include "integer.h" /* Basic integer types */ +#include "ffconf.h" /* FatFs configuration options */ + +#if _FATFS != _FFCONF +#error Wrong configuration file (ffconf.h). +#endif + +#ifdef WITH_FILESYSTEM + + + +/* Definitions of volume management */ + +#if _MULTI_PARTITION /* Multiple partition configuration */ +typedef struct { + BYTE pd; /* Physical drive number */ + BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */ +} PARTITION; +extern PARTITION VolToPart[]; /* Volume - Partition resolution table */ +#define LD2PD(vol) (VolToPart[vol].pd) /* Get physical drive number */ +#define LD2PT(vol) (VolToPart[vol].pt) /* Get partition index */ + +#else /* Single partition configuration */ +#define LD2PD(vol) (BYTE)(vol) /* Each logical drive is bound to the same physical drive number */ +#define LD2PT(vol) 0 /* Find first valid partition or in SFD */ + +#endif + + + +/* Type of path name strings on FatFs API */ + +#if _LFN_UNICODE /* Unicode string */ +#if !_USE_LFN +#error _LFN_UNICODE must be 0 in non-LFN cfg. +#endif +#ifndef _INC_TCHAR +typedef WCHAR TCHAR; +#define _T(x) L ## x +#define _TEXT(x) L ## x +#endif + +#else /* ANSI/OEM string */ +#ifndef _INC_TCHAR +typedef char TCHAR; +#define _T(x) x +#define _TEXT(x) x +#endif + +#endif + +/* File access control feature */ +#if _FS_LOCK +#if _FS_READONLY +#error _FS_LOCK must be 0 at read-only cfg. +#endif +struct FATFS; //Forward decl + +typedef struct { + FATFS *fs; /* Object ID 1, volume (NULL:blank entry) */ + DWORD clu; /* Object ID 2, directory */ + WORD idx; /* Object ID 3, directory index */ + WORD ctr; /* Object open counter, 0:none, 0x01..0xFF:read mode open count, 0x100:write mode */ +} FILESEM; +#endif + + +/* File system object structure (FATFS) */ + +struct FATFS { + BYTE fs_type; /* FAT sub-type (0:Not mounted) */ + //BYTE drv; /* Physical drive number */ + BYTE csize; /* Sectors per cluster (1,2,4...128) */ + BYTE n_fats; /* Number of FAT copies (1 or 2) */ + BYTE wflag; /* win[] flag (b0:dirty) */ + BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ + WORD id; /* File system mount ID */ + WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ +#if _MAX_SS != 512 + WORD ssize; /* Bytes per sector (512, 1024, 2048 or 4096) */ +#endif +#if _FS_REENTRANT + _SYNC_t sobj; /* Identifier of sync object */ +#endif +#if !_FS_READONLY + DWORD last_clust; /* Last allocated cluster */ + DWORD free_clust; /* Number of free clusters */ +#endif +#if _FS_RPATH + DWORD cdir; /* Current directory start cluster (0:root) */ +#endif + DWORD n_fatent; /* Number of FAT entries (= number of clusters + 2) */ + DWORD fsize; /* Sectors per FAT */ + DWORD volbase; /* Volume start sector */ + DWORD fatbase; /* FAT start sector */ + DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) */ + DWORD database; /* Data start sector */ + DWORD winsect; /* Current sector appearing in the win[] */ + BYTE win[_MAX_SS] __attribute__((aligned(4))); /* Disk access window for Directory, FAT (and file data at tiny cfg) */ +#if _USE_LFN == 1 + WCHAR LfnBuf[_MAX_LFN+1]; +#endif +#if _FS_LOCK + FILESEM Files[_FS_LOCK];/* Open object lock semaphores */ +#endif + miosix::intrusive_ref_ptr drv; /* drive device */ +}; + + + +/* File object structure (FIL) */ + +typedef struct { + FATFS* fs; /* Pointer to the related file system object (**do not change order**) */ + WORD id; /* Owner file system mount ID (**do not change order**) */ + BYTE flag; /* File status flags */ + BYTE err; /* Abort flag (error code) */ + DWORD fptr; /* File read/write pointer (Zeroed on file open) */ + DWORD fsize; /* File size */ + DWORD sclust; /* File data start cluster (0:no data cluster, always 0 when fsize is 0) */ + DWORD clust; /* Current cluster of fpter */ + DWORD dsect; /* Current data sector of fpter */ +#if !_FS_READONLY + DWORD dir_sect; /* Sector containing the directory entry */ + BYTE* dir_ptr; /* Pointer to the directory entry in the window */ +#endif +#if _USE_FASTSEEK + DWORD* cltbl; /* Pointer to the cluster link map table (Nulled on file open) */ +#endif +#if _FS_LOCK + UINT lockid; /* File lock ID (index of file semaphore table Files[]) */ +#endif +#if !_FS_TINY + BYTE buf[_MAX_SS]; /* File data read/write buffer */ +#endif +} FIL; + + + +/* Directory object structure (DIR) */ + +typedef struct { + FATFS* fs; /* Pointer to the owner file system object (**do not change order**) */ + WORD id; /* Owner file system mount ID (**do not change order**) */ + WORD index; /* Current read/write index number */ + DWORD sclust; /* Table start cluster (0:Root dir) */ + DWORD clust; /* Current cluster */ + DWORD sect; /* Current sector */ + BYTE* dir; /* Pointer to the current SFN entry in the win[] */ + BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */ +#if _FS_LOCK + UINT lockid; /* File lock ID (index of file semaphore table Files[]) */ +#endif +#if _USE_LFN + WCHAR* lfn; /* Pointer to the LFN working buffer */ + WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */ +#endif +} DIR_; + + + +/* File status structure (FILINFO) */ + +typedef struct { + DWORD fsize; /* File size */ + WORD fdate; /* Last modified date */ + WORD ftime; /* Last modified time */ + BYTE fattrib; /* Attribute */ + TCHAR fname[13]; /* Short file name (8.3 format) */ +#if _USE_LFN + /*TCHAR*/char *lfname; /* Pointer to the LFN buffer */ + UINT lfsize; /* Size of LFN buffer in TCHAR */ +#endif + unsigned int inode; //By TFT: support inodes +} FILINFO; + + + +/* File function return code (FRESULT) */ + +typedef enum { + FR_OK = 0, /* (0) Succeeded */ + FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ + FR_INT_ERR, /* (2) Assertion failed */ + FR_NOT_READY, /* (3) The physical drive cannot work */ + FR_NO_FILE, /* (4) Could not find the file */ + FR_NO_PATH, /* (5) Could not find the path */ + FR_INVALID_NAME, /* (6) The path name format is invalid */ + FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ + FR_EXIST, /* (8) Access denied due to prohibited access */ + FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ + FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FR_NOT_ENABLED, /* (12) The volume has no work area */ + FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any parameter error */ + FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_SHARE */ + FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ +} FRESULT; + + + +/*--------------------------------------------------------------*/ +/* FatFs module application interface */ + +FRESULT f_open (FATFS *fs, FIL* fp, const /*TCHAR*/char *path, BYTE mode); /* Open or create a file */ +FRESULT f_close (FIL* fp); /* Close an open file object */ +FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */ +FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */ +FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */ +FRESULT f_lseek (FIL* fp, DWORD ofs); /* Move file pointer of a file object */ +FRESULT f_truncate (FIL* fp); /* Truncate file */ +FRESULT f_sync (FIL* fp); /* Flush cached data of a writing file */ +FRESULT f_opendir (FATFS *fs, DIR_* dp, const /*TCHAR*/char *path); /* Open a directory */ +FRESULT f_closedir (DIR_* dp); /* Close an open directory */ +FRESULT f_readdir (DIR_* dp, FILINFO* fno); /* Read a directory item */ +FRESULT f_mkdir (FATFS *fs, const /*TCHAR*/char *path); /* Create a sub directory */ +FRESULT f_unlink (FATFS *fs, const /*TCHAR*/char *path); /* Delete an existing file or directory */ +FRESULT f_rename (FATFS *fs, const /*TCHAR*/char *path_old, const /*TCHAR*/char *path_new); /* Rename/Move a file or directory */ +FRESULT f_stat (FATFS *fs, const /*TCHAR*/char *path, FILINFO* fno); /* Get file status */ +FRESULT f_chmod (FATFS *fs, const /*TCHAR*/char *path, BYTE value, BYTE mask); /* Change attribute of the file/dir */ +FRESULT f_utime (FATFS *fs, const /*TCHAR*/char *path, const FILINFO* fno); /* Change times-tamp of the file/dir */ +FRESULT f_chdir (FATFS *fs, const TCHAR* path); /* Change current directory */ +FRESULT f_chdrive (const TCHAR* path); /* Change current drive */ +FRESULT f_getcwd (FATFS *fs, TCHAR* buff, UINT len); /* Get current directory */ +FRESULT f_getfree (FATFS *fs, /*const TCHAR* path,*/ DWORD* nclst/*, FATFS** fatfs*/); /* Get number of free clusters on the drive */ +FRESULT f_getlabel (FATFS *fs, const TCHAR* path, TCHAR* label, DWORD* sn); /* Get volume label */ +FRESULT f_setlabel (FATFS *fs, const TCHAR* label); /* Set volume label */ +FRESULT f_mount (FATFS* fs, /*const TCHAR* path,*/ BYTE opt, bool umount); /* Mount/Unmount a logical drive */ +FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au); /* Create a file system on the volume */ +FRESULT f_fdisk (BYTE pdrv, const DWORD szt[], void* work); /* Divide a physical drive into some partitions */ +int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */ +int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */ +int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */ +TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */ + +#define f_eof(fp) (((fp)->fptr == (fp)->fsize) ? 1 : 0) +#define f_error(fp) ((fp)->err) +#define f_tell(fp) ((fp)->fptr) +#define f_size(fp) ((fp)->fsize) + +#ifndef EOF +#define EOF (-1) +#endif + + + + +/*--------------------------------------------------------------*/ +/* Additional user defined functions */ + +/* RTC function */ +#if !_FS_READONLY +DWORD get_fattime (void); +#endif + +/* Unicode support functions */ +#if _USE_LFN /* Unicode - OEM code conversion */ +WCHAR ff_convert (WCHAR chr, UINT dir); /* OEM-Unicode bidirectional conversion */ +WCHAR ff_wtoupper (WCHAR chr); /* Unicode upper-case conversion */ +#if _USE_LFN == 3 /* Memory functions */ +void* ff_memalloc (UINT msize); /* Allocate memory block */ +void ff_memfree (void* mblock); /* Free memory block */ +#endif +#endif + +/* Sync functions */ +#if _FS_REENTRANT +int ff_cre_syncobj (BYTE vol, _SYNC_t* sobj); /* Create a sync object */ +int ff_req_grant (_SYNC_t sobj); /* Lock sync object */ +void ff_rel_grant (_SYNC_t sobj); /* Unlock sync object */ +int ff_del_syncobj (_SYNC_t sobj); /* Delete a sync object */ +#endif + + + + +/*--------------------------------------------------------------*/ +/* Flags and offset address */ + + +/* File access control and file status flags (FIL.flag) */ + +#define FA_READ 0x01 +#define FA_OPEN_EXISTING 0x00 + +#if !_FS_READONLY +#define FA_WRITE 0x02 +#define FA_CREATE_NEW 0x04 +#define FA_CREATE_ALWAYS 0x08 +#define FA_OPEN_ALWAYS 0x10 +#define FA__WRITTEN 0x20 +#define FA__DIRTY 0x40 +#endif + + +/* FAT sub type (FATFS.fs_type) */ + +#define FS_FAT12 1 +#define FS_FAT16 2 +#define FS_FAT32 3 + + +/* File attribute bits for directory entry */ + +#define AM_RDO 0x01 /* Read only */ +#define AM_HID 0x02 /* Hidden */ +#define AM_SYS 0x04 /* System */ +#define AM_VOL 0x08 /* Volume label */ +#define AM_LFN 0x0F /* LFN entry */ +#define AM_DIR 0x10 /* Directory */ +#define AM_ARC 0x20 /* Archive */ +#define AM_MASK 0x3F /* Mask of defined bits */ + + +/* Fast seek feature */ +#define CREATE_LINKMAP 0xFFFFFFFF + + + +/*--------------------------------*/ +/* Multi-byte word access macros */ + +#if _WORD_ACCESS == 1 /* Enable word access to the FAT structure */ +#define LD_WORD(ptr) (WORD)(*(WORD*)(BYTE*)(ptr)) +#define LD_DWORD(ptr) (DWORD)(*(DWORD*)(BYTE*)(ptr)) +#define ST_WORD(ptr,val) *(WORD*)(BYTE*)(ptr)=(WORD)(val) +#define ST_DWORD(ptr,val) *(DWORD*)(BYTE*)(ptr)=(DWORD)(val) +#else /* Use byte-by-byte access to the FAT structure */ +#define LD_WORD(ptr) (WORD)(((WORD)*((BYTE*)(ptr)+1)<<8)|(WORD)*(BYTE*)(ptr)) +#define LD_DWORD(ptr) (DWORD)(((DWORD)*((BYTE*)(ptr)+3)<<24)|((DWORD)*((BYTE*)(ptr)+2)<<16)|((WORD)*((BYTE*)(ptr)+1)<<8)|*(BYTE*)(ptr)) +#define ST_WORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *((BYTE*)(ptr)+1)=(BYTE)((WORD)(val)>>8) +#define ST_DWORD(ptr,val) *(BYTE*)(ptr)=(BYTE)(val); *((BYTE*)(ptr)+1)=(BYTE)((WORD)(val)>>8); *((BYTE*)(ptr)+2)=(BYTE)((DWORD)(val)>>16); *((BYTE*)(ptr)+3)=(BYTE)((DWORD)(val)>>24) +#endif + +#endif //WITH_FILESYSTEM + +//#ifdef __cplusplus +//} +//#endif + +#endif /* _FATFS */ diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/ffconf.h b/lib/miosix-kernel/miosix/filesystem/fat32/ffconf.h new file mode 100644 index 00000000..059519aa --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/ffconf.h @@ -0,0 +1,220 @@ +/*---------------------------------------------------------------------------/ +/ FatFs - FAT file system module configuration file R0.10 (C)ChaN, 2013 +/----------------------------------------------------------------------------/ +/ +/ CAUTION! Do not forget to make clean the project after any changes to +/ the configuration options. +/ +/----------------------------------------------------------------------------*/ +#ifndef _FFCONF +#define _FFCONF 80960 /* Revision ID */ + + +/*---------------------------------------------------------------------------/ +/ Functions and Buffer Configurations +/----------------------------------------------------------------------------*/ + +#define _FS_TINY 0 /* 0:Normal or 1:Tiny */ +/* When _FS_TINY is set to 1, FatFs uses the sector buffer in the file system +/ object instead of the sector buffer in the individual file object for file +/ data transfer. This reduces memory consumption 512 bytes each file object. */ + + +#define _FS_READONLY 0 /* 0:Read/Write or 1:Read only */ +/* Setting _FS_READONLY to 1 defines read only configuration. This removes +/ writing functions, f_write(), f_sync(), f_unlink(), f_mkdir(), f_chmod(), +/ f_rename(), f_truncate() and useless f_getfree(). */ + + +#define _FS_MINIMIZE 0 /* 0 to 3 */ +/* The _FS_MINIMIZE option defines minimization level to remove API functions. +/ +/ 0: All basic functions are enabled. +/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_chmod(), f_utime(), +/ f_truncate() and f_rename() function are removed. +/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. +/ 3: f_lseek() function is removed in addition to 2. */ + + +#define _USE_STRFUNC 0 /* 0:Disable or 1-2:Enable */ +/* To enable string functions, set _USE_STRFUNC to 1 or 2. */ + + +#define _USE_MKFS 0 /* 0:Disable or 1:Enable */ +/* To enable f_mkfs() function, set _USE_MKFS to 1 and set _FS_READONLY to 0 */ + + +#define _USE_FASTSEEK 0 /* 0:Disable or 1:Enable */ +/* To enable fast seek feature, set _USE_FASTSEEK to 1. */ + + +#define _USE_LABEL 0 /* 0:Disable or 1:Enable */ +/* To enable volume label functions, set _USE_LAVEL to 1 */ + + +#define _USE_FORWARD 0 /* 0:Disable or 1:Enable */ +/* To enable f_forward() function, set _USE_FORWARD to 1 and set _FS_TINY to 1. */ + + +/*---------------------------------------------------------------------------/ +/ Locale and Namespace Configurations +/----------------------------------------------------------------------------*/ + +#define _CODE_PAGE 1252 +/* The _CODE_PAGE specifies the OEM code page to be used on the target system. +/ Incorrect setting of the code page can cause a file open failure. +/ +/ 932 - Japanese Shift-JIS (DBCS, OEM, Windows) +/ 936 - Simplified Chinese GBK (DBCS, OEM, Windows) +/ 949 - Korean (DBCS, OEM, Windows) +/ 950 - Traditional Chinese Big5 (DBCS, OEM, Windows) +/ 1250 - Central Europe (Windows) +/ 1251 - Cyrillic (Windows) +/ 1252 - Latin 1 (Windows) +/ 1253 - Greek (Windows) +/ 1254 - Turkish (Windows) +/ 1255 - Hebrew (Windows) +/ 1256 - Arabic (Windows) +/ 1257 - Baltic (Windows) +/ 1258 - Vietnam (OEM, Windows) +/ 437 - U.S. (OEM) +/ 720 - Arabic (OEM) +/ 737 - Greek (OEM) +/ 775 - Baltic (OEM) +/ 850 - Multilingual Latin 1 (OEM) +/ 858 - Multilingual Latin 1 + Euro (OEM) +/ 852 - Latin 2 (OEM) +/ 855 - Cyrillic (OEM) +/ 866 - Russian (OEM) +/ 857 - Turkish (OEM) +/ 862 - Hebrew (OEM) +/ 874 - Thai (OEM, Windows) +/ 1 - ASCII (Valid for only non-LFN cfg.) +*/ + +//Note by TFT: DEF_NAMEBUF is used in the following functions: f_open(), +//f_opendir(), f_readdir(), f_stat(), f_unlink(), f_mkdir(), f_chmod(), +//f_utime(), f_rename(), and Miosix always locks a mutex before calling any of, +//these. For this reason it was chosen to allocate the LFN buffer statically, +//since the mutex protects from concurrent access to the buffer. +#define _USE_LFN 1 /* 0 to 3 */ +#define _MAX_LFN 255 /* Maximum LFN length to handle (12 to 255) */ +/* The _USE_LFN option switches the LFN feature. +/ +/ 0: Disable LFN feature. _MAX_LFN has no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT reentrant. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ To enable LFN feature, Unicode handling functions ff_convert() and ff_wtoupper() +/ function must be added to the project. +/ The LFN working buffer occupies (_MAX_LFN + 1) * 2 bytes. When use stack for the +/ working buffer, take care on stack overflow. When use heap memory for the working +/ buffer, memory management functions, ff_memalloc() and ff_memfree(), must be added +/ to the project. */ + +//Note by TFT: we do want unicode and not ancient code pages +#define _LFN_UNICODE 1 /* 0:ANSI/OEM or 1:Unicode */ +/* To switch the character encoding on the FatFs API to Unicode, enable LFN feature +/ and set _LFN_UNICODE to 1. */ + + +#define _STRF_ENCODE 3 /* 0:ANSI/OEM, 1:UTF-16LE, 2:UTF-16BE, 3:UTF-8 */ +/* When Unicode API is enabled, character encoding on the all FatFs API is switched +/ to Unicode. This option selects the character encoding on the file to be read/written +/ via string functions, f_gets(), f_putc(), f_puts and f_printf(). +/ This option has no effect when _LFN_UNICODE is 0. */ + + +#define _FS_RPATH 0 /* 0 to 2 */ +/* The _FS_RPATH option configures relative path feature. +/ +/ 0: Disable relative path feature and remove related functions. +/ 1: Enable relative path. f_chdrive() and f_chdir() function are available. +/ 2: f_getcwd() function is available in addition to 1. +/ +/ Note that output of the f_readdir() fnction is affected by this option. */ + + +/*---------------------------------------------------------------------------/ +/ Drive/Volume Configurations +/----------------------------------------------------------------------------*/ + +#define _VOLUMES 1 +/* Number of volumes (logical drives) to be used. */ + + +#define _MULTI_PARTITION 0 /* 0:Single partition, 1:Enable multiple partition */ +/* When set to 0, each volume is bound to the same physical drive number and +/ it can mount only first primaly partition. When it is set to 1, each volume +/ is tied to the partitions listed in VolToPart[]. */ + + +#define _MAX_SS 512 /* 512, 1024, 2048 or 4096 */ +/* Maximum sector size to be handled. +/ Always set 512 for memory card and hard disk but a larger value may be +/ required for on-board flash memory, floppy disk and optical disk. +/ When _MAX_SS is larger than 512, it configures FatFs to variable sector size +/ and GET_SECTOR_SIZE command must be implemented to the disk_ioctl() function. */ + + +#define _USE_ERASE 0 /* 0:Disable or 1:Enable */ +/* To enable sector erase feature, set _USE_ERASE to 1. Also CTRL_ERASE_SECTOR command +/ should be added to the disk_ioctl() function. */ + + +#define _FS_NOFSINFO 0 /* 0 or 1 */ +/* If you need to know the correct free space on the FAT32 volume, set this +/ option to 1 and f_getfree() function at first time after volume mount will +/ force a full FAT scan. +/ +/ 0: Load all informations in the FSINFO if available. +/ 1: Do not trust free cluster count in the FSINFO. +*/ + + + +/*---------------------------------------------------------------------------/ +/ System Configurations +/----------------------------------------------------------------------------*/ + +#define _WORD_ACCESS 0 /* 0 or 1 */ +/* The _WORD_ACCESS option is an only platform dependent option. It defines +/ which access method is used to the word data on the FAT volume. +/ +/ 0: Byte-by-byte access. Always compatible with all platforms. +/ 1: Word access. Do not choose this unless under both the following conditions. +/ +/ * Byte order on the memory is little-endian. +/ * Address miss-aligned word access is always allowed for all instructions. +/ +/ If it is the case, _WORD_ACCESS can also be set to 1 to improve performance +/ and reduce code size. +*/ + + +/* A header file that defines sync object types on the O/S, such as +/ windows.h, ucos_ii.h and semphr.h, must be included prior to ff.h. */ + +//Note by TFT: the reentrant option uses just one big lock for each FATFS, so +//given there's no concurrency advantage in using this option, we're just using +//an ordinary FastMutex in class Fat32Fs and locking it before calling FatFs. +#define _FS_REENTRANT 0 /* 0:Disable or 1:Enable */ +#define _FS_TIMEOUT 1000 /* Timeout period in unit of time ticks */ +#define _SYNC_t HANDLE /* O/S dependent type of sync object. e.g. HANDLE, OS_EVENT*, ID and etc.. */ + +/* The _FS_REENTRANT option switches the re-entrancy (thread safe) of the FatFs module. +/ +/ 0: Disable re-entrancy. _SYNC_t and _FS_TIMEOUT have no effect. +/ 1: Enable re-entrancy. Also user provided synchronization handlers, +/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() +/ function must be added to the project. */ + +//Note by TFT: this is very useful, as it avoids the danger of opening the same +//file for writing multiple times +#define _FS_LOCK 8 /* 0:Disable or >=1:Enable */ +/* To enable file lock control feature, set _FS_LOCK to 1 or greater. + The value defines how many files can be opened simultaneously. */ + + +#endif /* _FFCONFIG */ diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/integer.h b/lib/miosix-kernel/miosix/filesystem/fat32/integer.h new file mode 100644 index 00000000..074a46bd --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/integer.h @@ -0,0 +1,33 @@ +/*-------------------------------------------*/ +/* Integer type definitions for FatFs module */ +/*-------------------------------------------*/ + +#ifndef _FF_INTEGER +#define _FF_INTEGER + +#ifdef _WIN32 /* FatFs development platform */ + +#include +#include + +#else /* Embedded platform */ + +/* This type MUST be 8 bit */ +typedef unsigned char BYTE; + +/* These types MUST be 16 bit */ +typedef short SHORT; +typedef unsigned short WORD; +typedef unsigned short WCHAR; + +/* These types MUST be 16 bit or 32 bit */ +typedef int INT; +typedef unsigned int UINT; + +/* These types MUST be 32 bit */ +typedef long LONG; +typedef unsigned long DWORD; + +#endif + +#endif diff --git a/lib/miosix-kernel/miosix/filesystem/fat32/wtoupper.cpp b/lib/miosix-kernel/miosix/filesystem/fat32/wtoupper.cpp new file mode 100644 index 00000000..03ea0ff2 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/fat32/wtoupper.cpp @@ -0,0 +1,154 @@ + +#include "ff.h" +#include "config/miosix_settings.h" + +/* + * This is an alternative version of ff_wtoupper(), designed to be both smaller, + * faster and to better conform to the unicode specification. + * + * Code size using gcc 4.7.3, with + * arm-miosix-eabi-g++ -mcpu=cortex-m3 -mthumb -O2 -fno-exceptions -fno-rtti \ + * -c wtoupper.cpp + * is: + * - ChaN's ff_wtoupper(): 1000bytes + * - fede.tft's enhanced version: 236bytes + * + * The design of this function is a bit tricky, as the usual way of making a + * look up table is not optimized enough. It is old wisdom that a lut is + * both faster and more space-efficient than a sequence of if, but unicode + * conversion is somewhat peculiar. First, the input set is made of 0x10ffff + * possible values, so the usual design that makes lut access O(1) would + * require more than 2MB and is therefore out of question. However, the number + * of characters that have an uppercase form is just around 1000, so the next + * straightforward implementation would be to make a table of lowercase and + * a table of upperacse characters. A character is checked against each entry + * of the lowercase table, and if it matches the corresponding entry in the + * upperacse table is returned, while if it matches none then the character + * is either already uppercase, or does not have an uppercase form. + * + * This works, but requires roughly 4KB of tables, and is not very fast as it + * requires a for loop and a thousand comparisons per converted character. + * The next thing to notice is that many characters to convert are in + * contiguous ranges, which can be dealt with using an if statement per range. + * There is a tradeoff, however, as the range needs to contain more than a + * certain number of elements to be faster and/or result in less code size than + * the lut approach. For this reason, it was selected to use an if for every + * range of 7 or more code points, and use a final round with a look up table + * to deal with too small ranges. + * + * For what concerns unicode conformance, the result has been checked against + * the file UnicodeData.txt downloaded from unicode's website, and the following + * functional modifications have been done with respect to the original + * ff_wtoupper(): + * - Code points 0x00a1, 0x00a2, 0x003, 0x00a5, 0x00ac, 0x00af are no longer + * converted. This is because they do not have an uppercase form + * - Code point 0x00b5 is converted to 0x039c + * - Code point 0x0131 is converted to 0x0049 and not 0x130 + * + * In addition, according to UnicodeData.txt there are many character that + * were missing from the original implementation of ff_wtoupper(), but this was + * not fixed, as it would lead to significantly larger tables. + */ + +#ifdef WITH_FILESYSTEM + +static const unsigned short lowerCase[]= +{ + 0x00b5, 0x00ff, 0x0131, 0x0133, 0x0135, 0x0137, 0x017a, 0x017c, + 0x017e, 0x0192, 0x045e, 0x045f, +}; + +static const unsigned short upperCase[]= +{ + 0x039c, 0x0178, 0x0049, 0x0132, 0x0134, 0x0136, 0x0179, 0x017b, + 0x017d, 0x0191, 0x040e, 0x040f, +}; + +static const int tabSize=sizeof(lowerCase)/sizeof(lowerCase[0]); + +unsigned short ff_wtoupper(unsigned short c) +{ + if(c>='a' && c<='z') return c-('a'-'A'); //26 code points + + if(c<0x80) return c;//Speed hack: there are no other lowercase char in ASCII + + if(c>=0x00e0 && c<=0x00f6) return c-(0x00e0-0x00c0); //23 code points + + if(c>=0x00f8 && c<=0x00fe) return c-(0x00f8-0x00d8); // 7 code points + + if(c>=0x0101 && c<=0x012f && (c & 1)) return c-1; //24 code points + + if(c>=0x013a && c<=0x0148 && ((c & 1)==0)) return c-1; // 8 code points + + if(c>=0x014b && c<=0x0177 && (c & 1)) return c-1; //23 code points + + if(c>=0x03b1 && c<=0x03c1) return c-(0x03b1-0x0391); //17 code points + + if(c>=0x03c3 && c<=0x03ca) return c-(0x03c3-0x03a3); // 8 code points + + if(c>=0x0430 && c<=0x044f) return c-(0x0430-0x0410); //32 code points + + if(c>=0x0451 && c<=0x045c) return c-(0x0451-0x0401); //12 code points + + if(c>=0x2170 && c<=0x217f) return c-(0x2170-0x2160); //16 code points + + if(c>=0xff41 && c<=0xff5a) return c-(0xff41-0xff21); //26 code points + + for(int i=0;i +#include + +using namespace std; + +int main() +{ + for(unisgned short i=0;i<0x10000;i++) + { + unisgned short up=ff_wtoupper(i); + if(up==i) continue; + cout< +#include +#include +#include + +using namespace std; + +enum { + codePoint=0, + upperCase=12, + lowerCase=13 +}; + +int main() +{ + ifstream in("UnicodeData.txt"); + string line; + int lineno=0; + while(getline(in,line)) + { + lineno++; + stringstream ss(line); + vector fields; + string field; + while(getline(ss,field,';')) fields.push_back(field); + if(fields.at(upperCase).empty()==false && + fields.at(codePoint)!=fields.at(upperCase)) + cout< * + ***************************************************************************/ + +#include "file.h" +#include +#include +#include +#include "file_access.h" +#include "config/miosix_settings.h" + +using namespace std; + +namespace miosix { + +//This is a static assert. The filesystem code assumes off_t is 64bit. +typedef char check_sizeof_off_t[sizeof(off_t)==8 ? 1 : -1]; + +// +// class FileBase +// + +FileBase::FileBase(intrusive_ref_ptr parent) : parent(parent) +{ + if(parent) parent->newFileOpened(); +} + +#ifdef WITH_FILESYSTEM + +int FileBase::isatty() const +{ + return 0; +} + +int FileBase::fcntl(int cmd, int opt) +{ + //Newlib makes some calls to fcntl, for example in opendir(). CLOEXEC isn't + //supported, but for now we lie and return 0 + if(cmd==F_SETFD && (opt==FD_CLOEXEC || opt==0)) return 0; + return -EBADF; +} + +int FileBase::ioctl(int cmd, void *arg) +{ + return -ENOTTY; //Means the operation does not apply to this descriptor +} + +int FileBase::getdents(void *dp, int len) +{ + return -EBADF; +} + +#endif //WITH_FILESYSTEM + +FileBase::~FileBase() +{ + if(parent) parent->fileCloseHook(); +} + +// +// class DirectoryBase +// + +ssize_t DirectoryBase::write(const void *data, size_t len) +{ + return -EBADF; +} + +ssize_t DirectoryBase::read(void *data, size_t len) +{ + return -EBADF; +} + +off_t DirectoryBase::lseek(off_t pos, int whence) +{ + return -EBADF; +} + +int DirectoryBase::fstat(struct stat *pstat) const +{ + return -EBADF; +} + +int DirectoryBase::addEntry(char **pos, char *end, int ino, char type, + const StringPart& n) +{ + int reclen=direntHeaderSizeNoPadding+n.length()+1; + reclen=(reclen+3) & ~0x3; //Align to 4 bytes + if(reclen>end-*pos) return -1; + + struct dirent *data=reinterpret_cast(*pos); + data->d_ino=ino; + data->d_off=0; + data->d_reclen=reclen; + data->d_type=type; + strcpy(data->d_name,n.c_str()); + + (*pos)+=reclen; + return reclen; +} + +int DirectoryBase::addDefaultEntries(char **pos, int thisIno, int upIno) +{ + struct dirent *data=reinterpret_cast(*pos); + data->d_ino=thisIno; + data->d_off=0; + data->d_reclen=direntHeaderSize; + data->d_type=DT_DIR; + strcpy(data->d_name,"."); + + (*pos)+=direntHeaderSize; + data=reinterpret_cast(*pos); + data->d_ino=upIno; + data->d_off=0; + data->d_reclen=direntHeaderSize; + data->d_type=DT_DIR; + strcpy(data->d_name,".."); + + (*pos)+=direntHeaderSize; + return 2*direntHeaderSize; +} + +int DirectoryBase::addTerminatingEntry(char **pos, char *end) +{ + if(direntHeaderSize>end-*pos) return -1; + //This sets everything to zero, including d_reclen, terminating the + //directory listing loop in readdir.c + memset(*pos,0,direntHeaderSize); + (*pos)+=direntHeaderSize; + return direntHeaderSize; +} + +// +// class FilesystemBase +// + +FilesystemBase::FilesystemBase() : +#ifdef WITH_FILESYSTEM + filesystemId(FilesystemManager::getFilesystemId()), +#else //WITH_FILESYSTEM + filesystemId(0), +#endif //WITH_FILESYSTEM + parentFsMountpointInode(1), openFileCount(0) {} + +int FilesystemBase::readlink(StringPart& name, string& target) +{ + return -EINVAL; //Default implementation, for filesystems without symlinks +} + +bool FilesystemBase::supportsSymlinks() const { return false; } + +void FilesystemBase::newFileOpened() { atomicAdd(&openFileCount,1); } + +void FilesystemBase::fileCloseHook() +{ + #ifdef WITH_ERRLOG + int result=atomicAddExchange(&openFileCount,-1); + assert(result>=0); + #else //WITH_ERRLOG + atomicAdd(&openFileCount,-1); + #endif //WITH_ERRLOG +} + +FilesystemBase::~FilesystemBase() {} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/file.h b/lib/miosix-kernel/miosix/filesystem/file.h new file mode 100644 index 00000000..b857f0fe --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/file.h @@ -0,0 +1,379 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include "kernel/intrusive.h" +#include "config/miosix_settings.h" + +#ifndef FILE_H +#define FILE_H + +namespace miosix { + +// Forward decls +class FilesystemBase; +class StringPart; + +/** + * The unix file abstraction. Also some device drivers are seen as files. + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + */ +class FileBase : public IntrusiveRefCounted +{ +public: + /** + * Constructor + * \param parent the filesystem to which this file belongs + */ + FileBase(intrusive_ref_ptr parent); + + /** + * Write data to the file, if the file supports writing. + * \param data the data to write + * \param len the number of bytes to write + * \return the number of written characters, or a negative number in case + * of errors + */ + virtual ssize_t write(const void *data, size_t len)=0; + + /** + * Read data from the file, if the file supports reading. + * \param data buffer to store read data + * \param len the number of bytes to read + * \return the number of read characters, or a negative number in case + * of errors + */ + virtual ssize_t read(void *data, size_t len)=0; + + #ifdef WITH_FILESYSTEM + + /** + * Move file pointer, if the file supports random-access. + * \param pos offset to sum to the beginning of the file, current position + * or end of file, depending on whence + * \param whence SEEK_SET, SEEK_CUR or SEEK_END + * \return the offset from the beginning of the file if the operation + * completed, or a negative number in case of errors + */ + virtual off_t lseek(off_t pos, int whence)=0; + + /** + * Return file information. + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + virtual int fstat(struct stat *pstat) const=0; + + /** + * Check whether the file refers to a terminal. + * \return 1 if it is a terminal, 0 if it is not, or a negative number in + * case of errors + */ + virtual int isatty() const; + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param opt optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + virtual int fcntl(int cmd, int opt); + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + virtual int ioctl(int cmd, void *arg); + + /** + * Also directories can be opened as files. In this case, this system call + * allows to retrieve directory entries. + * \param dp pointer to a memory buffer where one or more struct dirent + * will be placed. dp must be four words aligned. + * \param len memory buffer size. + * \return the number of bytes read on success, or a negative number on + * failure. + */ + virtual int getdents(void *dp, int len); + + /** + * \return a pointer to the parent filesystem + */ + const intrusive_ref_ptr getParent() const { return parent; } + + #endif //WITH_FILESYSTEM + + /** + * File destructor + */ + virtual ~FileBase(); + +private: + FileBase(const FileBase&); + FileBase& operator=(const FileBase&); + + intrusive_ref_ptr parent; ///< Files may have a parent fs +}; + +/** + * Directories are a special kind of files that implement the getdents() call + * Classes of this type are reference counted, must be allocated on the heap + * and managed through intrusive_ref_ptr + */ +class DirectoryBase : public FileBase +{ +public: + /** + * Constructor + * \param parent the filesystem to which this file belongs + */ + DirectoryBase(intrusive_ref_ptr parent) : FileBase(parent) {} + + /** + * Write data to the file, if the file supports writing. + * \param data the data to write + * \param len the number of bytes to write + * \return the number of written characters, or a negative number in case + * of errors + */ + virtual ssize_t write(const void *data, size_t len); + + /** + * Read data from the file, if the file supports reading. + * \param data buffer to store read data + * \param len the number of bytes to read + * \return the number of read characters, or a negative number in case + * of errors + */ + virtual ssize_t read(void *data, size_t len); + + /** + * Move file pointer, if the file supports random-access. + * \param pos offset to sum to the beginning of the file, current position + * or end of file, depending on whence + * \param whence SEEK_SET, SEEK_CUR or SEEK_END + * \return the offset from the beginning of the file if the operation + * completed, or a negative number in case of errors + */ + virtual off_t lseek(off_t pos, int whence); + + /** + * Return file information. + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + virtual int fstat(struct stat *pstat) const; + +protected: + /** + * Helper function to add a directory entry to a buffer + * \param pos position where to add the entry (four word aligned). Pointer + * is incremented. + * \param end end of buffer (one char past the last), for bound checking + * \param ino inode of file + * \param type file type + * \param name file name to append after the DirentHeader + * \return the number of bytes written or -1 on failure (no space in buffer) + */ + static int addEntry(char **pos, char *end, int ino, char type, + const StringPart& n); + + /** + * Helper function to add the default directory entries . and .. to a buffer + * \param pos position where to add the entry (four word aligned). Pointer + * is incremented. The caller is responsible to guarantee that there is at + * least space for 2*direntHeaderSize + * \param thisIno inode number of . + * \param upInode inode number of .. + * \return the number of bytes written + */ + static int addDefaultEntries(char **pos, int thisIno, int upIno); + + /** + * Add an entry with d_reclen=0 which is used to terminate directory listing + * \param pos position where to add the entry (four word aligned). Pointer + * is incremented. The caller is responsible to guarantee that there is at + * least space for direntHeaderSize, including padding + * \param end end of buffer (one char past the last), for bound checking + * \return the number of bytes written or -1 on failure (no space in buffer) + */ + static int addTerminatingEntry(char **pos, char *end); + + ///Size of struct dirent excluding d_name. That is, the size of d_ino, + ///d_off, d_reclen and d_type. Notice that there are 4 bytes of padding + ///between d_ino and d_off as d_off is a 64 bit number. Should be 19. + static const int direntHeaderSizeNoPadding=offsetof(struct dirent,d_name); + + ///Size of struct dirent including room for the "." and ".." string in + ///d_name, including terminating \0 and padding for 4-word alignment. + ///First +3: make room for '..\0', 3 bytes + ///Second +3 and /4*4: four word alignment + static const int direntHeaderSize=(direntHeaderSizeNoPadding+3+3)/4*4; + + ///Minimum buffer accepted by getdents, two for . and .., plus terminating + static const int minimumBufferSize=3*direntHeaderSize; +}; + +/** + * All filesystems derive from this class. Classes of this type are reference + * counted, must be allocated on the heap and managed through + * intrusive_ref_ptr + */ +class FilesystemBase : public IntrusiveRefCounted, + public IntrusiveRefCountedSharedFromThis +{ +public: + /** + * Constructor + */ + FilesystemBase(); + + /** + * Open a file + * \param file the file object will be stored here, if the call succeeds + * \param name the name of the file to open, relative to the local + * filesystem + * \param flags file flags (open for reading, writing, ...) + * \param mode file permissions + * \return 0 on success, or a negative number on failure + */ + virtual int open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode)=0; + + /** + * Obtain information on a file, identified by a path name. Does not follow + * symlinks + * \param name path name, relative to the local filesystem + * \param pstat file information is stored here + * \return 0 on success, or a negative number on failure + */ + virtual int lstat(StringPart& name, struct stat *pstat)=0; + + /** + * Remove a file or directory + * \param name path name of file or directory to remove + * \return 0 on success, or a negative number on failure + */ + virtual int unlink(StringPart& name)=0; + + /** + * Rename a file or directory + * \param oldName old file name + * \param newName new file name + * \return 0 on success, or a negative number on failure + */ + virtual int rename(StringPart& oldName, StringPart& newName)=0; + + /** + * Create a directory + * \param name directory name + * \param mode directory permissions + * \return 0 on success, or a negative number on failure + */ + virtual int mkdir(StringPart& name, int mode)=0; + + /** + * Remove a directory if empty + * \param name directory name + * \return 0 on success, or a negative number on failure + */ + virtual int rmdir(StringPart& name)=0; + + /** + * Follows a symbolic link + * \param path path identifying a symlink, relative to the local filesystem + * \param target the link target is returned here if the call succeeds. + * Note that the returned path is not relative to this filesystem, and can + * be either relative or absolute. + * \return 0 on success, a negative number on failure + */ + virtual int readlink(StringPart& name, std::string& target); + + /** + * \return true if the filesystem supports symbolic links. + * In this case, the filesystem should override readlink + */ + virtual bool supportsSymlinks() const; + + /** + * \internal + * \return true if all files belonging to this filesystem are closed + */ + bool areAllFilesClosed() { return openFileCount==0; } + + /** + * \internal + * Called by file constructor whenever a file belonging to this + * filesystem is opened. Never call this function from user code. + */ + void newFileOpened(); + + /** + * \internal + * Called by file destructors whenever a file belonging to this + * filesystem is closed. Never call this function from user code. + */ + void fileCloseHook(); + + /** + * \internal + * This is used to inform a filesystem of the inode of the directory in the + * parent fs where it is mounted. It is used for directory listing, to + * resolve the inode of the .. entry of the filesystem's root directory + * \param inode inode of the directory where the fs is mounted + */ + void setParentFsMountpointInode(int inode) { parentFsMountpointInode=inode; } + + /** + * \return filesystem id + */ + short int getFsId() const { return filesystemId; } + + /** + * Destructor + */ + virtual ~FilesystemBase(); + +protected: + + const short int filesystemId; ///< The unique filesystem id, used by lstat + int parentFsMountpointInode; ///< The inode of the directory in the parent fs + +private: + FilesystemBase(const FilesystemBase&); + FilesystemBase& operator= (const FilesystemBase&); + + volatile int openFileCount; ///< Number of open files +}; + +} //namespace miosix + +#endif //FILE_H diff --git a/lib/miosix-kernel/miosix/filesystem/file_access.cpp b/lib/miosix-kernel/miosix/filesystem/file_access.cpp new file mode 100644 index 00000000..74d8e5a6 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/file_access.cpp @@ -0,0 +1,763 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "file_access.h" +#include +#include +#include +#include "console/console_device.h" +#include "mountpointfs/mountpointfs.h" +#include "fat32/fat32.h" +#include "kernel/logging.h" +#ifdef WITH_PROCESSES +#include "kernel/process.h" +#endif //WITH_PROCESSES + +using namespace std; + +#ifdef WITH_FILESYSTEM + +namespace miosix { + +/* + * A note on the use of strings in this file. This file uses three string + * types: C string, C++ std::string and StringPart which is an efficent + * in-place substring of either a C or C++ string. + * + * The functions which are meant to be used by clients of the filesystem + * API take file names as C strings. This is becase that's the signature + * of the POSIX calls, such as fopen(), stat(), umount(), ... It is + * worth noticing that these C strings are const. However, resolving paths + * requires a writable scratchpad string to be able to remove + * useless path components, such as "/./", go backwards when a "/../" is + * found, and follow symbolic links. To this end, all these functions + * make a copy of the passed string into a temporary C++ string that + * will be deallocated as soon as the function returns. + * Resolving a path, however, requires to scan all its path components + * one by one, checking if the path up to that point is a symbolic link + * or a mountpoint of a filesystem. + * For example, resolving "/home/test/file" requires to make three + * substrings, "/home", "/home/test" and "/home/test/file". Of course, + * one can simply use the substr() member function of the std::string + * class. However, this means making lots of tiny memory allocations on + * the heap, increasing the RAM footprint to resolve a path. To this end, + * the StringPart class was introduced, to create fast in-place substring + * of a string. + */ + +// +// class FileDescriptorTable +// + +FileDescriptorTable::FileDescriptorTable() + : mutex(FastMutex::RECURSIVE), cwd("/") +{ + FilesystemManager::instance().addFileDescriptorTable(this); + files[0]=files[1]=files[2]=intrusive_ref_ptr( + new TerminalDevice(DefaultConsole::instance().get())); +} + +FileDescriptorTable::FileDescriptorTable(const FileDescriptorTable& rhs) + : mutex(FastMutex::RECURSIVE), cwd(rhs.cwd) +{ + //No need to lock the mutex since we are in a constructor and there can't + //be pointers to this in other threads yet + for(int i=0;ifiles[i]=atomic_load(&rhs.files[i]); + FilesystemManager::instance().addFileDescriptorTable(this); +} + +FileDescriptorTable& FileDescriptorTable::operator=( + const FileDescriptorTable& rhs) +{ + Lock l(mutex); + for(int i=0;ifiles[i],atomic_load(&rhs.files[i])); + return *this; +} + +int FileDescriptorTable::open(const char* name, int flags, int mode) +{ + if(name==0 || name[0]=='\0') return -EFAULT; + Lock l(mutex); + for(int i=3;iopen(files[i],sp,flags,mode); + if(result==0) return i; //The file descriptor + else return result; //The error code + } + return -ENFILE; +} + +int FileDescriptorTable::close(int fd) +{ + //No need to lock the mutex when deleting + if(fd<0 || fd>=MAX_OPEN_FILES) return -EBADF; + intrusive_ref_ptr toClose; + toClose=atomic_exchange(files+fd,intrusive_ref_ptr()); + if(!toClose) return -EBADF; //File entry was not open + return 0; +} + +void FileDescriptorTable::closeAll() +{ + for(int i=0;i()); +} + +int FileDescriptorTable::getcwd(char *buf, size_t len) +{ + if(buf==0 || len<2) return -EINVAL; //We don't support the buf==0 extension + Lock l(mutex); + struct stat st; + if(stat(".",&st) || !S_ISDIR(st.st_mode)) return -ENOENT; + if(cwd.length()>len) return -ERANGE; + strncpy(buf,cwd.c_str(),len); + if(cwd.length()>1) buf[cwd.length()-1]='\0'; //Erase last '/' in cwd + return 0; +} + +int FileDescriptorTable::chdir(const char* name) +{ + if(name==0 || name[0]=='\0') return -EFAULT; + size_t len=strlen(name); + if(name[len-1]!='/') len++; //Reserve room for trailing slash + Lock l(mutex); + if(name[0]!='/') len+=cwd.length(); + if(len>PATH_MAX) return -ENAMETOOLONG; + + string newCwd; + newCwd.reserve(len); + if(name[0]=='/') newCwd=name; + else { + newCwd=cwd; + newCwd+=name; + } + ResolvedPath openData=FilesystemManager::instance().resolvePath(newCwd); + if(openData.result<0) return openData.result; + struct stat st; + StringPart sp(newCwd,string::npos,openData.off); + if(int result=openData.fs->lstat(sp,&st)) return result; + if(!S_ISDIR(st.st_mode)) return -ENOTDIR; + //NOTE: put after resolvePath() as it strips trailing / + //Also put after lstat() as it fails if path has a trailing slash + newCwd+='/'; + cwd=newCwd; + return 0; +} + +int FileDescriptorTable::mkdir(const char *name, int mode) +{ + if(name==0 || name[0]=='\0') return -EFAULT; + string path=absolutePath(name); + if(path.empty()) return -ENAMETOOLONG; + ResolvedPath openData=FilesystemManager::instance().resolvePath(path,true); + if(openData.result<0) return openData.result; + StringPart sp(path,string::npos,openData.off); + return openData.fs->mkdir(sp,mode); +} + +int FileDescriptorTable::rmdir(const char *name) +{ + if(name==0 || name[0]=='\0') return -EFAULT; + string path=absolutePath(name); + if(path.empty()) return -ENAMETOOLONG; + ResolvedPath openData=FilesystemManager::instance().resolvePath(path,true); + if(openData.result<0) return openData.result; + StringPart sp(path,string::npos,openData.off); + return openData.fs->rmdir(sp); +} + +int FileDescriptorTable::unlink(const char *name) +{ + if(name==0 || name[0]=='\0') return -EFAULT; + string path=absolutePath(name); + if(path.empty()) return -ENAMETOOLONG; + return FilesystemManager::instance().unlinkHelper(path); +} + +int FileDescriptorTable::rename(const char *oldName, const char *newName) +{ + if(oldName==0 || oldName[0]=='\0') return -EFAULT; + if(newName==0 || newName[0]=='\0') return -EFAULT; + string oldPath=absolutePath(oldName); + string newPath=absolutePath(newName); + if(oldPath.empty() || newPath.empty()) return -ENAMETOOLONG; + return FilesystemManager::instance().renameHelper(oldPath,newPath); +} + +int FileDescriptorTable::statImpl(const char* name, struct stat* pstat, bool f) +{ + if(name==0 || name[0]=='\0' || pstat==0) return -EFAULT; + string path=absolutePath(name); + if(path.empty()) return -ENAMETOOLONG; + return FilesystemManager::instance().statHelper(path,pstat,f); +} + +FileDescriptorTable::~FileDescriptorTable() +{ + FilesystemManager::instance().removeFileDescriptorTable(this); + //There's no need to lock the mutex and explicitly close files eventually + //left open, because if there are other threads accessing this while we are + //being deleted we have bigger problems anyway +} + +string FileDescriptorTable::absolutePath(const char* path) +{ + size_t len=strlen(path); + if(len>PATH_MAX) return ""; + if(path[0]=='/') return path; + Lock l(mutex); + if(len+cwd.length()>PATH_MAX) return ""; + return cwd+path; +} + +/** + * This class implements the path resolution logic + */ +class PathResolution +{ +public: + /** + * Constructor + * \param fs map of all mounted filesystems + */ + PathResolution(const map >& fs) + : filesystems(fs) {} + + /** + * The main purpose of this class, resolve a path + * \param path inout parameter with the path to resolve. The resolved path + * will be modified in-place in this string. The path must be absolute and + * start with a "/". The caller is responsible for that. + * \param followLastSymlink if true, follow last symlink + * \return a resolved path + */ + ResolvedPath resolvePath(string& path, bool followLastSymlink); + +private: + /** + * Handle a /../ in a path + * \param path path string + * \param slash path[slash] is the / character after the .. + * \return 0 on success, a negative number on error + */ + int upPathComponent(string& path, size_t slash); + + /** + * Handle a normal path component in a path, i.e, a path component + * that is neither //, /./ or /../ + * \param path path string + * \param followIfSymlink if true, follow symbolic links + * \return 0 on success, or a negative number on error + */ + int normalPathComponent(string& path, bool followIfSymlink); + + /** + * Follow a symbolic link + * \param path path string. The relative path into the current filesystem + * must be a symbolic link (verified by the caller). + * \return 0 on success, a negative number on failure + */ + int followSymlink(string& path); + + /** + * Find to which filesystem this path belongs + * \param path path string. + * \return 0 on success, a negative number on failure + */ + int recursiveFindFs(string& path); + + /// Mounted filesystems + const map >& filesystems; + + /// Pointer to root filesystem + intrusive_ref_ptr root; + + /// Current filesystem while looking up path + intrusive_ref_ptr fs; + + /// True if current filesystem supports symlinks + bool syms; + + /// path[index] is first unhandled char + size_t index; + + /// path.substr(indexIntoFs) is the relative path to current filesystem + size_t indexIntoFs; + + /// How many components does the relative path have in current fs + int depthIntoFs; + + /// How many symlinks we've found so far + int linksFollowed; + + /// Maximum number of symbolic links to follow (to avoid endless loops) + static const int maxLinkToFollow=2; +}; + +ResolvedPath PathResolution::resolvePath(string& path, bool followLastSymlink) +{ + map >::const_iterator it; + it=filesystems.find(StringPart("/")); + if(it==filesystems.end()) return ResolvedPath(-ENOENT); //should not happen + root=fs=it->second; + syms=fs->supportsSymlinks(); + index=1; //Skip leading / + indexIntoFs=1; //NOTE: caller must ensure path[0]=='/' + depthIntoFs=1; + linksFollowed=0; + for(;;) + { + size_t slash=path.find_first_of('/',index); + //cout<path.length() ? followLastSymlink : true; + int result=normalPathComponent(path,follow); + if(result<0) return ResolvedPath(result); + } + //Last component + if(index>=path.length()) + { + //Remove trailing / + size_t last=path.length()-1; + if(path[last]=='/') + { + path.erase(last,1); + //This may happen if the last path component is a fs + if(indexIntoFs>path.length()) indexIntoFs=path.length(); + } + return ResolvedPath(fs,indexIntoFs); + } + } +} + +int PathResolution::upPathComponent(string& path, size_t slash) +{ + if(index<=1) return -ENOENT; //root dir has no parent + size_t removeStart=path.find_last_of('/',index-2); + if(removeStart==string::npos) return -ENOENT; //should not happen + path.erase(removeStart,slash-removeStart); + index=removeStart+1; + //This may happen when merging a path like "/dir/.." + if(path.empty()) path='/'; + //This may happen if the new last path component is a fs, e.g. "/dev/null/.." + if(indexIntoFs>path.length()) indexIntoFs=path.length(); + if(--depthIntoFs>0) return 0; + + //Depth went to zero, escape current filesystem + return recursiveFindFs(path); +} + +int PathResolution::normalPathComponent(string& path, bool followIfSymlink) +{ + map >::const_iterator it; + it=filesystems.find(StringPart(path,index-1)); + if(it!=filesystems.end()) + { + //Jumped to a new filesystem. Not stat-ing the path as we're + //relying on mount not allowing to mount a filesystem on anything + //but a directory. + fs=it->second; + syms=fs->supportsSymlinks(); + indexIntoFs=index>path.length() ? index-1 : index; + depthIntoFs=1; + return 0; + } + depthIntoFs++; + if(syms && followIfSymlink) + { + struct stat st; + { + StringPart sp(path,index-1,indexIntoFs); + if(int res=fs->lstat(sp,&st)<0) return res; + } + if(S_ISLNK(st.st_mode)) return followSymlink(path); + else if(index<=path.length() && !S_ISDIR(st.st_mode)) return -ENOTDIR; + } + return 0; +} + +int PathResolution::followSymlink(string& path) +{ + if(++linksFollowed>=maxLinkToFollow) return -ELOOP; + string target; + { + StringPart sp(path,index-1,indexIntoFs); + if(int res=fs->readlink(sp,target)<0) return res; + } + if(target.empty()) return -ENOENT; //Should not happen + if(target[0]=='/') + { + //Symlink is absolute + size_t newPathLen=target.length()+path.length()-index+1; + if(newPathLen>PATH_MAX) return -ENAMETOOLONG; + string newPath; + newPath.reserve(newPathLen); + newPath=target; + if(index<=path.length()) + newPath.insert(newPath.length(),path,index-1,string::npos); + path.swap(newPath); + fs=root; + syms=root->supportsSymlinks(); + index=1; + indexIntoFs=1; + depthIntoFs=1; + } else { + //Symlink is relative + size_t removeStart=path.find_last_of('/',index-2); + size_t newPathLen=path.length()-(index-removeStart-2)+target.length(); + if(newPathLen>PATH_MAX) return -ENAMETOOLONG; + string newPath; + newPath.reserve(newPathLen); + newPath.insert(0,path,0,removeStart+1); + newPath+=target; + if(index<=path.length()) + newPath.insert(newPath.length(),path,index-1,string::npos); + path.swap(newPath); + index=removeStart+1; + depthIntoFs--; + } + return 0; +} + +int PathResolution::recursiveFindFs(string& path) +{ + depthIntoFs=1; + size_t backIndex=index; + for(;;) + { + backIndex=path.find_last_of('/',backIndex-1); + if(backIndex==string::npos) return -ENOENT; //should not happpen + if(backIndex==0) + { + fs=root; + indexIntoFs=1; + break; + } + map >::const_iterator it; + it=filesystems.find(StringPart(path,backIndex)); + if(it!=filesystems.end()) + { + fs=it->second; + indexIntoFs=backIndex+1; + break; + } + depthIntoFs++; + } + syms=fs->supportsSymlinks(); + return 0; +} + +// +// class FilesystemManager +// + +FilesystemManager& FilesystemManager::instance() +{ + static FilesystemManager instance; + return instance; +} + +int FilesystemManager::kmount(const char* path, intrusive_ref_ptr fs) +{ + if(path==0 || path[0]=='\0' || fs==0) return -EFAULT; + Lock l(mutex); + size_t len=strlen(path); + if(len>PATH_MAX) return -ENAMETOOLONG; + string temp(path); + if(!(temp=="/" && filesystems.empty())) //Skip check when mounting / + { + struct stat st; + if(int result=statHelper(temp,&st,false)) return result; + if(!S_ISDIR(st.st_mode)) return -ENOTDIR; + string parent=temp+"/.."; + if(int result=statHelper(parent,&st,false)) return result; + fs->setParentFsMountpointInode(st.st_ino); + } + if(filesystems.insert(make_pair(StringPart(temp),fs)).second==false) + return -EBUSY; //Means already mounted + else + return 0; +} + +int FilesystemManager::umount(const char* path, bool force) +{ + typedef + typename map >::iterator fsIt; + + if(path==0 || path[0]=='\0') return -ENOENT; + size_t len=strlen(path); + if(len>PATH_MAX) return -ENAMETOOLONG; + Lock l(mutex); //A reader-writer lock would be better + fsIt it=filesystems.find(StringPart(path)); + if(it==filesystems.end()) return -EINVAL; + + //This finds all the filesystems that have to be recursively umounted + //to umount the required filesystem. For example, if /path and /path/path2 + //are filesystems, umounting /path must umount also /path/path2 + vector fsToUmount; + for(fsIt it2=filesystems.begin();it2!=filesystems.end();++it2) + if(it2->first.startsWith(it->first)) fsToUmount.push_back(it2); + + //Now look into all file descriptor tables if there are open files in the + //filesystems to umount. If there are, return busy. This is an heavy + //operation given the way the filesystem data structure is organized, but + //it has been done like this to minimize the size of an entry in the file + //descriptor table (4 bytes), and because umount happens infrequently. + //Note that since we are locking the same mutex used by resolvePath(), + //other threads can't open new files concurrently while we check + #ifdef WITH_PROCESSES + list::iterator it3; + for(it3=fileTables.begin();it3!=fileTables.end();++it3) + { + for(int i=0;i file=(*it3)->getFile(i); + if(!file) continue; + vector::iterator it4; + for(it4=fsToUmount.begin();it4!=fsToUmount.end();++it4) + { + if(file->getParent()!=(*it4)->second) continue; + if(force==false) return -EBUSY; + (*it3)->close(i); //If forced umount, close the file + } + } + } + #else //WITH_PROCESSES + for(int i=0;i file=getFileDescriptorTable().getFile(i); + if(!file) continue; + vector::iterator it4; + for(it4=fsToUmount.begin();it4!=fsToUmount.end();++it4) + { + if(file->getParent()!=(*it4)->second) continue; + if(force==false) return -EBUSY; + getFileDescriptorTable().close(i);//If forced umount, close the file + } + } + #endif //WITH_PROCESSES + + //Now there should be no more files belonging to the filesystems to umount, + //but check if it is really so, as there is a possible race condition + //which is the read/close,umount where one thread performs a read (or write) + //operation on a file descriptor, it gets preempted and another thread does + //a close on that descriptor and an umount of the filesystem. Also, this may + //happen in case of a forced umount. In such a case there is no entry in + //the descriptor table (as close was called) but the operation is still + //ongoing. + vector::iterator it5; + const int maxRetry=3; //Retry up to three times + for(int i=0;isecond->areAllFilesClosed()) continue; + if(force==false) return -EBUSY; + failed=true; + break; + } + if(!failed) break; + if(i==maxRetry-1) return -EBUSY; //Failed to umount even if forced + Thread::sleep(1000); //Wait to see if the filesystem operation completes + } + + //It is now safe to umount all filesystems + for(it5=fsToUmount.begin();it5!=fsToUmount.end();++it5) + filesystems.erase(*it5); + return 0; +} + +void FilesystemManager::umountAll() +{ + Lock l(mutex); + #ifdef WITH_PROCESSES + list::iterator it; + for(it=fileTables.begin();it!=fileTables.end();++it) (*it)->closeAll(); + #else //WITH_PROCESSES + getFileDescriptorTable().closeAll(); + #endif //WITH_PROCESSES + filesystems.clear(); +} + +ResolvedPath FilesystemManager::resolvePath(string& path, bool followLastSymlink) +{ + //see man path_resolution. This code supports arbitrarily mounted + //filesystems, symbolic links resolution, but no hardlinks to directories + if(path.length()>PATH_MAX) return ResolvedPath(-ENAMETOOLONG); + if(path.empty() || path[0]!='/') return ResolvedPath(-ENOENT); + + Lock l(mutex); + PathResolution pr(filesystems); + return pr.resolvePath(path,followLastSymlink); +} + +int FilesystemManager::unlinkHelper(string& path) +{ + //Do everything while keeping the mutex locked to prevent someone to + //concurrently mount a filesystem on the directory we're unlinking + Lock l(mutex); + ResolvedPath openData=resolvePath(path,true); + if(openData.result<0) return openData.result; + //After resolvePath() so path is in canonical form and symlinks are followed + if(filesystems.find(StringPart(path))!=filesystems.end()) return -EBUSY; + StringPart sp(path,string::npos,openData.off); + return openData.fs->unlink(sp); +} + +int FilesystemManager::statHelper(string& path, struct stat *pstat, bool f) +{ + ResolvedPath openData=resolvePath(path,f); + if(openData.result<0) return openData.result; + StringPart sp(path,string::npos,openData.off); + return openData.fs->lstat(sp,pstat); +} + +int FilesystemManager::renameHelper(string& oldPath, string& newPath) +{ + //Do everything while keeping the mutex locked to prevent someone to + //concurrently mount a filesystem on the directory we're renaming + Lock l(mutex); + ResolvedPath oldOpenData=resolvePath(oldPath,true); + if(oldOpenData.result<0) return oldOpenData.result; + ResolvedPath newOpenData=resolvePath(newPath,true); + if(newOpenData.result<0) return newOpenData.result; + + if(oldOpenData.fs!=newOpenData.fs) return -EXDEV; //Can't rename across fs + + //After resolvePath() so path is in canonical form and symlinks are followed + if(filesystems.find(StringPart(oldPath))!=filesystems.end()) return -EBUSY; + if(filesystems.find(StringPart(newPath))!=filesystems.end()) return -EBUSY; + + StringPart oldSp(oldPath,string::npos,oldOpenData.off); + StringPart newSp(newPath,string::npos,newOpenData.off); + + //Can't rename a directory into a subdirectory of itself + if(newSp.startsWith(oldSp)) return -EINVAL; + return oldOpenData.fs->rename(oldSp,newSp); +} + +short int FilesystemManager::getFilesystemId() +{ + return atomicAddExchange(&devCount,1); +} + +int FilesystemManager::devCount=1; + +#ifdef WITH_DEVFS +intrusive_ref_ptr //return value is a pointer to DevFs +#else //WITH_DEVFS +void //return value is void +#endif //WITH_DEVFS +basicFilesystemSetup(intrusive_ref_ptr dev) +{ + bootlog("Mounting MountpointFs as / ... "); + FilesystemManager& fsm=FilesystemManager::instance(); + intrusive_ref_ptr rootFs(new MountpointFs); + bootlog(fsm.kmount("/",rootFs)==0 ? "Ok\n" : "Failed\n"); + + #ifdef WITH_DEVFS + bootlog("Mounting DevFs as /dev ... "); + StringPart sp("dev"); + int r1=rootFs->mkdir(sp,0755); + intrusive_ref_ptr devfs(new DevFs); + int r2=fsm.kmount("/dev",devfs); + bool devFsOk=(r1==0 && r2==0); + bootlog(devFsOk ? "Ok\n" : "Failed\n"); + if(!devFsOk) return devfs; + fsm.setDevFs(devfs); + #endif //WITH_DEVFS + + bootlog("Mounting Fat32Fs as /sd ... "); + bool fat32failed=false; + intrusive_ref_ptr disk; + #ifdef WITH_DEVFS + if(dev) devfs->addDevice("sda",dev); + StringPart sda("sda"); + if(devfs->open(disk,sda,O_RDWR,0)<0) fat32failed=true; + #else //WITH_DEVFS + if(dev && dev->open(disk,intrusive_ref_ptr(0),O_RDWR,0)<0) + fat32failed=true; + #endif //WITH_DEVFS + + intrusive_ref_ptr fat32; + if(fat32failed==false) + { + fat32=new Fat32Fs(disk); + if(fat32->mountFailed()) fat32failed=true; + } + if(fat32failed==false) + { + StringPart sd("sd"); + fat32failed=rootFs->mkdir(sd,0755)!=0; + fat32failed=fsm.kmount("/sd",fat32)!=0; + } + bootlog(fat32failed==0 ? "Ok\n" : "Failed\n"); + + #ifdef WITH_DEVFS + return devfs; + #endif //WITH_DEVFS +} + +FileDescriptorTable& getFileDescriptorTable() +{ + #ifdef WITH_PROCESSES + //Something like + return Thread::getCurrentThread()->getProcess()->getFileTable(); + #else //WITH_PROCESSES + static FileDescriptorTable fileTable; ///< The only file table + return fileTable; + #endif //WITH_PROCESSES +} + +} //namespace miosix + +#endif //WITH_FILESYSTEM diff --git a/lib/miosix-kernel/miosix/filesystem/file_access.h b/lib/miosix-kernel/miosix/filesystem/file_access.h new file mode 100644 index 00000000..2f8d1b44 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/file_access.h @@ -0,0 +1,553 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef FILE_ACCESS_H +#define FILE_ACCESS_H + +#include +#include +#include +#include +#include +#include "file.h" +#include "stringpart.h" +#include "devfs/devfs.h" +#include "kernel/sync.h" +#include "kernel/intrusive.h" +#include "config/miosix_settings.h" + +#ifdef WITH_FILESYSTEM + +namespace miosix { + +/** + * The result of resolvePath(). + */ +class ResolvedPath +{ +public: + /** + * Constructor + */ + ResolvedPath() : result(-EINVAL), fs(0), off(0) {} + + /** + * Constructor + * \param result error code + */ + explicit ResolvedPath(int result) : result(result), fs(0), off(0) {} + + /** + * Constructor + * \param fs filesystem + * \param off offset into path where the subpath relative to the current + * filesystem starts + */ + ResolvedPath(intrusive_ref_ptr fs, size_t offset) + : result(0), fs(fs), off(offset) {} + + int result; ///< 0 on success, a negative number on failure + intrusive_ref_ptr fs; ///< pointer to the filesystem to which the file belongs + /// path.c_str()+off is a string containing the relative path into the + /// filesystem for the looked up file + size_t off; +}; + +/** + * This class maps file descriptors to file objects, allowing to + * perform file operations + */ +class FileDescriptorTable +{ +public: + /** + * Constructor + */ + FileDescriptorTable(); + + /** + * Copy constructor + * \param rhs object to copy from + */ + FileDescriptorTable(const FileDescriptorTable& rhs); + + /** + * Operator= + * \param rhs object to copy from + * \return *this + */ + FileDescriptorTable& operator=(const FileDescriptorTable& rhs); + + /** + * Open a file + * \param name file name + * \param flags file open mode + * \param mode allows to set file permissions + * \return a file descriptor, or a negative number on error + */ + int open(const char *name, int flags, int mode); + + /** + * Close a file + * \param fd file descriptor to close + * \return 0 on success or a negative number on failure + */ + int close(int fd); + + /** + * Close all files + */ + void closeAll(); + + /** + * Write data to the file, if the file supports writing. + * \param data the data to write + * \param len the number of bytes to write + * \return the number of written characters, or a negative number in case + * of errors + */ + ssize_t write(int fd, const void *data, size_t len) + { + if(data==0) return -EFAULT; + //Important, since len is specified by standard to be unsigned, but the + //return value has to be signed + if(static_cast(len)<0) return -EINVAL; + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->write(data,len); + } + + /** + * Read data from the file, if the file supports reading. + * \param data buffer to store read data + * \param len the number of bytes to read + * \return the number of read characters, or a negative number in case + * of errors + */ + ssize_t read(int fd, void *data, size_t len) + { + if(data==0) return -EFAULT; + //Important, since len is specified by standard to be unsigned, but the + //return value has to be signed + if(static_cast(len)<0) return -EINVAL; + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->read(data,len); + } + + /** + * Move file pointer, if the file supports random-access. + * \param pos offset to sum to the beginning of the file, current position + * or end of file, depending on whence + * \param whence SEEK_SET, SEEK_CUR or SEEK_END + * \return the offset from the beginning of the file if the operation + * completed, or a negative number in case of errors + */ + off_t lseek(int fd, off_t pos, int whence) + { + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->lseek(pos,whence); + } + + /** + * Return file information. + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + int fstat(int fd, struct stat *pstat) const + { + if(pstat==0) return -EFAULT; + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->fstat(pstat); + } + + /** + * Check whether the file refers to a terminal. + * \return 1 if it is a terminal, 0 if it is not, or a negative number in + * case of errors + */ + int isatty(int fd) const + { + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->isatty(); + } + + /** + * Return file information, follows last symlink + * \param path file to stat + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + int stat(const char *name, struct stat *pstat) + { + return statImpl(name,pstat,true); + } + + /** + * Return file information, does not follow last symlink + * \param path file to stat + * \param pstat pointer to stat struct + * \return 0 on success, or a negative number on failure + */ + int lstat(const char *name, struct stat *pstat) + { + return statImpl(name,pstat,false); + } + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param opt optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int fcntl(int fd, int cmd, int opt) + { + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->fcntl(cmd,opt); + } + + /** + * Perform various operations on a file descriptor + * \param cmd specifies the operation to perform + * \param arg optional argument that some operation require + * \return the exact return value depends on CMD, -1 is returned on error + */ + int ioctl(int fd, int cmd, void *arg) + { + //arg unchecked here, as some ioctl don't use it + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->ioctl(cmd,arg); + } + + /** + * List directory content + * \param dp dp pointer to a memory buffer where one or more struct dirent + * will be placed. dp must be four words aligned. + * \param len memory buffer size. + * \return the number of bytes read on success, or a negative number on + * failure. + */ + int getdents(int fd, void *dp, int len) + { + if(dp==0) return -EFAULT; + if(reinterpret_cast(dp) & 0x3) return -EFAULT; //Not aligned + intrusive_ref_ptr file=getFile(fd); + if(!file) return -EBADF; + return file->getdents(dp,len); + } + + /** + * Return current directory + * \param buf the current directory is stored here + * \param len buffer length, if it is not big enough, ERANGE is returned + * \return 0 on success, or a negative number on failure + */ + int getcwd(char *buf, size_t len); + + /** + * Change current directory + * \param path new current directory + * \return 0 on success, or a negative number on failure + */ + int chdir(const char *name); + + /** + * Create a directory + * \param name directory to create + * \param mode directory permissions + * \return 0 on success, or a negative number on failure + */ + int mkdir(const char *name, int mode); + + /** + * Remove a directory if empty + * \param name directory to create + * \return 0 on success, or a negative number on failure + */ + int rmdir(const char *name); + + /** + * Remove a file or directory + * \param name file or directory to remove + * \return 0 on success, or a negative number on failure + */ + int unlink(const char *name); + + /** + * Rename a file or directory + * \param oldName old file name + * \param newName new file name + * \return 0 on success, or a negative number on failure + */ + int rename(const char *oldName, const char *newName); + + /** + * Retrieves an entry in the file descriptor table + * \param fd file descriptor, index into the table + * \return a refcounted poiter to the file at the desired entry + * (which may be empty), or an empty refcounted pointer if the index is + * out of bounds + */ + intrusive_ref_ptr getFile(int fd) const + { + if(fd<0 || fd>=MAX_OPEN_FILES) return intrusive_ref_ptr(); + return atomic_load(files+fd); + } + + /** + * Destructor + */ + ~FileDescriptorTable(); + +private: + /** + * Append cwd to path if it is not an absolute path + * \param path an absolute or relative path, must not be null + * \return an absolute path, or an empty string if the path would exceed + * PATH_MAX + */ + std::string absolutePath(const char *path); + + /** + * Return file information (implements both stat and lstat) + * \param path file to stat + * \param pstat pointer to stat struct + * \param f true to follow last synlink (stat), + * false to not follow it (lstat) + * \return 0 on success, or a negative number on failure + */ + int statImpl(const char *name, struct stat *pstat, bool f); + + FastMutex mutex; ///< Locks on writes to file object pointers, not on accesses + + std::string cwd; ///< Current working directory + + /// Holds the mapping between fd and file objects + intrusive_ref_ptr files[MAX_OPEN_FILES]; +}; + +/** + * This class contains information on all the mounted filesystems + */ +class FilesystemManager +{ +public: + /** + * \return the instance of the filesystem manager (singleton) + */ + static FilesystemManager& instance(); + + /** + * Low level mount operation, meant to be used only inside the kernel, + * and board support packages. It is the only mount operation that can + * mount the root filesystem. + * \param path path where to mount the filesystem + * \param fs filesystem to mount. Ownership of the pointer is transferred + * to the FilesystemManager class + * \return 0 on success, a negative number on failure + */ + int kmount(const char *path, intrusive_ref_ptr fs); + + /** + * Unmounts a filesystem + * \param path path to a filesytem + * \param force true to umount the filesystem even if busy + * \return 0 on success, or a negative number on error + */ + int umount(const char *path, bool force=false); + + /** + * Umount all filesystems, to be called before system shutdown or reboot + */ + void umountAll(); + + #ifdef WITH_DEVFS + /** + * \return a pointer to the devfs, useful to add other device files + */ + intrusive_ref_ptr getDevFs() const { return atomic_load(&devFs); } + + /** + * Called by basicFilesystemSetup() or directly by the BSP to set the + * pointer returned by getDevFs() + * \param dev pointer to the DevFs + */ + void setDevFs(intrusive_ref_ptr dev) + { + atomic_store(&devFs,dev); + } + #endif //WITH_DEVFS + + /** + * Resolve a path to identify the filesystem it belongs + * \param path an absolute path name, that must start with '/'. Note that + * this is an inout parameter, the string is modified so as to return the + * full resolved path. In particular, the returned string differs from the + * passed one by not containing useless path components, such as "/./" and + * "//", by not containing back path componenets ("/../"), and may be + * entirely different from the passed one if a symlink was encountered + * during name resolution. The use of an inout parameter is to minimize + * the number of copies of the path string, optimizing for speed and size + * in the common case, but also means that a copy of the original string + * needs to be made if the original has to be used later. + * \param followLastSymlink true if the symlink in the last path component + *(the one that does not end with a /, if it exists, has to be followed) + * \return the resolved path + */ + ResolvedPath resolvePath(std::string& path, bool followLastSymlink=true); + + /** + * \internal + * Helper function to unlink a file or directory. Only meant to be used by + * FileDescriptorTable::unlink() + * \param path path of file or directory to unlink + * \return 0 on success, or a neagtive number on failure + */ + int unlinkHelper(std::string& path); + + /** + * \internal + * Helper function to stat a file or directory. Only meant to be used by + * FileDescriptorTable::statImpl() + * \param path path of file or directory to stat + * \param pstat pointer to stat struct + * \param f f true to follow last synlink (stat), + * false to not follow it (lstat) + * \return 0 on success, or a negative number on failure + */ + int statHelper(std::string& path, struct stat *pstat, bool f); + + /** + * \internal + * Helper function to unlink a file or directory. Only meant to be used by + * FileDescriptorTable::unlink() + * \param oldPath path of file or directory to unlink + * \param newPath path of file or directory to unlink + * \return 0 on success, or a neagtive number on failure + */ + int renameHelper(std::string& oldPath, std::string& newPath); + + /** + * \internal + * Called by FileDescriptorTable's constructor. Never call this function + * from user code. + */ + void addFileDescriptorTable(FileDescriptorTable *fdt) + { + #ifdef WITH_PROCESSES + if(isKernelRunning()) + { + Lock l(mutex); + fileTables.push_back(fdt); + } else { + //This function is also called before the kernel is started, + //and in this case it is forbidden to lock mutexes + fileTables.push_back(fdt); + } + #endif //WITH_PROCESSES + } + + /** + * \internal + * Called by FileDescriptorTable's constructor. Never call this function + * from user code. + */ + void removeFileDescriptorTable(FileDescriptorTable *fdt) + { + #ifdef WITH_PROCESSES + Lock l(mutex); + fileTables.remove(fdt); + #endif //WITH_PROCESSES + } + + /** + * \internal + * \return an unique id used to identify a filesystem, mostly for filling + * in the st_dev field when stat is called. + */ + static short int getFilesystemId(); + +private: + /** + * Constructor, private as it is a singleton + */ + FilesystemManager() : mutex(FastMutex::RECURSIVE) {} + + FilesystemManager(const FilesystemManager&); + FilesystemManager& operator=(const FilesystemManager&); + + FastMutex mutex; ///< To protect against concurrent access + + /// Mounted filesystem + std::map > filesystems; + + #ifdef WITH_PROCESSES + std::list fileTables; ///< Process file tables + #endif //WITH_PROCESSES + #ifdef WITH_DEVFS + intrusive_ref_ptr devFs; + #endif //WITH_DEVFS + + static int devCount; ///< For assigning filesystemId to filesystems +}; + +/** + * This is a simplified function to mount the root and /dev filesystems, + * meant to be called from bspInit2(). It mounts a MountpointFs as root, then + * creates a /dev directory, and mounts /dev there. It also takes the passed + * device and if it is not null it adds the device di DevFs as /dev/sda. + * Last, it attempts to mount /dev/sda at /sd as a Fat32 filesystem. + * In case the bsp needs another filesystem setup, such as having a fat32 + * filesystem as /, this function can't be used, but instead the bsp needs to + * mount the filesystems manually. + * \param dev disk device that will be added as /dev/sda and mounted on /sd + * \return a pointer to the DevFs, so as to be able to add other device files, + * but only if WITH_DEVFS is defined + */ +#ifdef WITH_DEVFS +intrusive_ref_ptr //return value is a pointer to DevFs +#else //WITH_DEVFS +void //return value is void +#endif //WITH_DEVFS +basicFilesystemSetup(intrusive_ref_ptr dev); + +/** + * \return a pointer to the file descriptor table associated with the + * current process. + */ +FileDescriptorTable& getFileDescriptorTable(); + +} //namespace miosix + +#endif //WITH_FILESYSTEM + +#endif //FILE_ACCESS_H diff --git a/lib/miosix-kernel/miosix/filesystem/ioctl.h b/lib/miosix-kernel/miosix/filesystem/ioctl.h new file mode 100644 index 00000000..72aabc96 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/ioctl.h @@ -0,0 +1,45 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef IOCTL_H +#define IOCTL_H + +namespace miosix { + +enum Ioctl +{ + IOCTL_SYNC=100, + IOCTL_TCGETATTR=101, + IOCTL_TCSETATTR_NOW=102, + IOCTL_TCSETATTR_FLUSH=103, + IOCTL_TCSETATTR_DRAIN=104, + IOCTL_FLUSH=105 +}; + +} + +#endif //IOCTL_H diff --git a/lib/miosix-kernel/miosix/filesystem/mountpointfs/mountpointfs.cpp b/lib/miosix-kernel/miosix/filesystem/mountpointfs/mountpointfs.cpp new file mode 100644 index 00000000..c331ac74 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/mountpointfs/mountpointfs.cpp @@ -0,0 +1,204 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "mountpointfs.h" +#include +#include +#include +#include "filesystem/stringpart.h" + +using namespace std; + +namespace miosix { + +#ifdef WITH_FILESYSTEM + +/** + * Directory class for MountpointFs + */ +class MountpointFsDirectory : public DirectoryBase +{ +public: + /** + * \param parent parent filesystem + * \param mutex mutex to lock when accessing the file map + * \param dirs file map + * \param root true if we're listing the root directory + * \param currentInode inode of the directory we're listing + * \param parentInode inode of the parent directory + */ + MountpointFsDirectory(intrusive_ref_ptr parent, + FastMutex& mutex, map& dirs, bool root, + int currentInode, int parentInode) + : DirectoryBase(parent), mutex(mutex), dirs(dirs), + currentInode(currentInode), parentInode(parentInode), + first(true), last(false) + { + Lock l(mutex); + if(root && dirs.empty()==false) currentItem=dirs.begin()->first.c_str(); + } + + /** + * Also directories can be opened as files. In this case, this system call + * allows to retrieve directory entries. + * \param dp pointer to a memory buffer where one or more struct dirent + * will be placed. dp must be four words aligned. + * \param len memory buffer size. + * \return the number of bytes read on success, or a negative number on + * failure. + */ + virtual int getdents(void *dp, int len); + +private: + FastMutex& mutex; ///< Mutex of parent class + std::map& dirs; ///< Directory entries of parent class + string currentItem; ///< First unhandled item in directory + int currentInode,parentInode; ///< Inodes of . and .. + + bool first; ///< True if first time getdents is called + bool last; ///< True if directory has ended +}; + +// +// class MountpointFsDirctory +// + +int MountpointFsDirectory::getdents(void *dp, int len) +{ + if(len l(mutex); + char *begin=reinterpret_cast(dp); + char *buffer=begin; + char *end=buffer+len; + if(first) + { + first=false; + addDefaultEntries(&buffer,currentInode,parentInode); + } + if(currentItem.empty()==false) + { + map::iterator it=dirs.find(StringPart(currentItem)); + //Someone deleted the exact directory entry we had saved (unlikely) + if(it==dirs.end()) return -EBADF; + for(;it!=dirs.end();++it) + { + if(addEntry(&buffer,end,it->second,DT_DIR,it->first)>0) continue; + //Buffer finished + currentItem=it->first.c_str(); + return buffer-begin; + } + } + addTerminatingEntry(&buffer,end); + last=true; + return buffer-begin; +} + +// +// class MountpointFs +// + +int MountpointFs::open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode) +{ + if(flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC)) + return -EACCES; + + Lock l(mutex); + int currentInode=rootDirInode; + int parentInode=parentFsMountpointInode; + if(name.empty()==false) + { + map::iterator it=dirs.find(name); + if(it==dirs.end()) return -EACCES; + parentInode=currentInode; + currentInode=it->second; + } + file=intrusive_ref_ptr( + new MountpointFsDirectory( + shared_from_this(),mutex,dirs,name.empty(),currentInode,parentInode)); + return 0; +} + +int MountpointFs::lstat(StringPart& name, struct stat *pstat) +{ + Lock l(mutex); + map::iterator it; + if(name.empty()==false) + { + it=dirs.find(name); + if(it==dirs.end()) return -ENOENT; + } + memset(pstat,0,sizeof(struct stat)); + pstat->st_dev=filesystemId; + pstat->st_ino=name.empty() ? rootDirInode : it->second; + pstat->st_mode=S_IFDIR | 0755; //drwxr-xr-x + pstat->st_nlink=1; + pstat->st_blksize=512; + return 0; +} + +int MountpointFs::unlink(StringPart& name) +{ + return -ENOENT; +} + +int MountpointFs::rename(StringPart& oldName, StringPart& newName) +{ + Lock l(mutex); + map::iterator it=dirs.find(oldName); + if(it==dirs.end()) return -ENOENT; + for(unsigned int i=0;isecond; + dirs.erase(it); + return 0; +} + +int MountpointFs::mkdir(StringPart& name, int mode) +{ + for(unsigned int i=0;i l(mutex); + if(dirs.insert(make_pair(name,inodeCount)).second==false) return -EEXIST; + inodeCount++; + return 0; +} + +int MountpointFs::rmdir(StringPart& name) +{ + Lock l(mutex); + if(dirs.erase(name)==1) return 0; + return -ENOENT; +} + +#endif //WITH_FILESYSTEM + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/mountpointfs/mountpointfs.h b/lib/miosix-kernel/miosix/filesystem/mountpointfs/mountpointfs.h new file mode 100644 index 00000000..1fa60865 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/mountpointfs/mountpointfs.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef MOUNTPOINTFS_H +#define MOUNTPOINTFS_H + +#include +#include "filesystem/file.h" +#include "kernel/sync.h" +#include "config/miosix_settings.h" + +namespace miosix { + +#ifdef WITH_FILESYSTEM + +/** + * MountpointFs is a special filesystem whose purpose is to create directories + * to be used as mountpoints for other filesystems. + */ +class MountpointFs : public FilesystemBase +{ +public: + /** + * Constructor + */ + MountpointFs() : mutex(FastMutex::RECURSIVE), inodeCount(rootDirInode+1) {} + + /** + * Open a file + * \param file the file object will be stored here, if the call succeeds + * \param name the name of the file to open, relative to the local + * filesystem + * \param flags file flags (open for reading, writing, ...) + * \param mode file permissions + * \return 0 on success, or a negative number on failure + */ + virtual int open(intrusive_ref_ptr& file, StringPart& name, + int flags, int mode); + + /** + * Obtain information on a file, identified by a path name. Does not follow + * symlinks + * \param name path name, relative to the local filesystem + * \param pstat file information is stored here + * \return 0 on success, or a negative number on failure + */ + virtual int lstat(StringPart& name, struct stat *pstat); + + /** + * Remove a file or directory + * \param name path name of file or directory to remove + * \return 0 on success, or a negative number on failure + */ + virtual int unlink(StringPart& name); + + /** + * Rename a file or directory + * \param oldName old file name + * \param newName new file name + * \return 0 on success, or a negative number on failure + */ + virtual int rename(StringPart& oldName, StringPart& newName); + + /** + * Create a directory + * \param name directory name + * \param mode directory permissions + * \return 0 on success, or a negative number on failure + */ + virtual int mkdir(StringPart& name, int mode); + + /** + * Remove a directory if empty + * \param name directory name + * \return 0 on success, or a negative number on failure + */ + virtual int rmdir(StringPart& name); + +private: + FastMutex mutex; + std::map dirs; + int inodeCount; + static const int rootDirInode=1; +}; + +#endif //WITH_FILESYSTEM + +} //namespace miosix + +#endif //MOUNTPOINTFS_H diff --git a/lib/miosix-kernel/miosix/filesystem/stringpart.cpp b/lib/miosix-kernel/miosix/filesystem/stringpart.cpp new file mode 100644 index 00000000..f7a180bc --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/stringpart.cpp @@ -0,0 +1,185 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +//Makes memrchr available in newer GCCs +#define _GNU_SOURCE +#include + +#include "stringpart.h" +#include + +using namespace std; + +namespace miosix { + +// +// class StringPart +// + +StringPart::StringPart(string& str, size_t idx, size_t off) + : str(&str), index(idx), offset(off), saved('\0'), owner(false), + type(CPPSTR) +{ + if(index==string::npos || index>=str.length()) index=str.length(); + else { + saved=str[index]; + str[index]='\0'; + } + offset=min(offset,index); +} + +StringPart::StringPart(char* s, size_t idx, size_t off) + : cstr(s), index(idx), offset(off), saved('\0'), owner(false), + type(CSTR) +{ + assert(cstr); //Passed pointer can't be null + size_t len=strlen(cstr); + if(index==string::npos || index>=len) index=len; + else { + saved=cstr[index]; + cstr[index]='\0'; + } + offset=min(offset,index); +} + +StringPart::StringPart(const char* s) + : ccstr(s), offset(0), saved('\0'), owner(false), type(CCSTR) +{ + assert(ccstr); //Passed pointer can't be null + index=strlen(s); +} + +StringPart::StringPart(StringPart& rhs, size_t idx, size_t off) + : saved('\0'), owner(false), type(rhs.type) +{ + switch(type) + { + case CSTR: + this->cstr=rhs.cstr; + break; + case CCSTR: + type=CSTR; //To make a substring of a CCSTR we need to make a copy + if(rhs.empty()==false) assign(rhs); else cstr=&saved; + break; + case CPPSTR: + this->str=rhs.str; + break; + } + if(idx!=string::npos && idxlength()c_str(),rhs.c_str(),rhs.length())==0; +} + +size_t StringPart::findLastOf(char c) const +{ + const char *begin=c_str(); + //Not strrchr() to take advantage of knowing the string length + void *index=memrchr(begin,c,length()); + if(index==0) return std::string::npos; + return reinterpret_cast(index)-begin; +} + +const char *StringPart::c_str() const +{ + switch(type) + { + case CSTR: return cstr+offset; + case CCSTR: return ccstr; //Offset always 0 + default: return str->c_str()+offset; + } +} + +char StringPart::operator[] (size_t index) const +{ + switch(type) + { + case CSTR: return cstr[offset+index]; + case CCSTR: return ccstr[index]; //Offset always 0 + default: return (*str)[offset+index]; + } +} + +void StringPart::clear() +{ + if(type==CSTR) + { + cstr[index]=saved;//Worst case we'll overwrite terminating \0 with an \0 + if(owner) delete[] cstr; + } else if(type==CPPSTR) { + if(index!=str->length()) (*str)[index]=saved; + if(owner) delete str; + } //For CCSTR there's nothing to do + cstr=&saved; //Reusing saved as an empty string + saved='\0'; + index=offset=0; + owner=false; + type=CSTR; +} + +void StringPart::assign(const StringPart& rhs) +{ + cstr=new char[rhs.length()+1]; + strcpy(cstr,rhs.c_str()); + index=rhs.length(); + offset=0; + owner=true; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/filesystem/stringpart.h b/lib/miosix-kernel/miosix/filesystem/stringpart.h new file mode 100644 index 00000000..645c9c57 --- /dev/null +++ b/lib/miosix-kernel/miosix/filesystem/stringpart.h @@ -0,0 +1,216 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef STRINGPART_H +#define STRINGPART_H + +#include +#include + +namespace miosix { + +/** + * \internal + * This class is used to take a substring of a string containing a file path + * without creating a copy, and therefore requiring additional memory + * allocation. + * + * When parsing a path like "/home/test/directory/file" it is often necessary + * to create substrings at the path component boundaries, such as "/home/test". + * In this case, it is possible to temporarily make a substring by replacing a + * '/' with a '\0'. The std::string will not 'forget' its orginal size, and + * when the '\0' will be converted back to a '/', the string will look identical + * to the previous one. + * + * This is an optimization made for filesystem mountpoint lookups, and is not + * believed to be useful outside of that purpose. Given that in Miosix, the + * mountpoints are stored in a map, this class supports operator< to correctly + * lookup mountpoints in the map. + */ +class StringPart +{ +public: + /** + * Default constructor + */ + StringPart() : cstr(&saved), index(0), offset(0), saved('\0'), + owner(false), type(CSTR) + { + //We need an empty C string, that is, a pointer to a char that is \0, + //so we make cstr point to saved, and set saved to \0. + } + + /** + * Constructor from C++ string + * \param str original string. A pointer to the string is taken, the string + * is NOT copied. Therefore, the caller is responsible to guarantee the + * string won't be deallocated while this class is alive. Note that the + * original string is modified by inserting a '\0' at the index position, + * if index is given. The string will be restored to the exact original + * content only when this class is destroyed. + * \param idx if this parameter is given, this class becomes an in-place + * substring of the original string. Otherwise, this class will store the + * entire string passed. In this case, the original string will not be + * modified. + * \param off if this parameter is given, the first off characters of the + * string are skipped. Note that idx calculations take place before + * offset computation, so idx is relative to the original string. + */ + explicit StringPart(std::string& str, size_t idx=std::string::npos, + size_t off=0); + + /** + * Constructor from C string + * \param s original string. A pointer to the string is taken, the string + * is NOT copied. Therefore, the caller is responsible to guarantee the + * string won't be deallocated while this class is alive. Note that the + * original string is modified by inserting a '\0' at the index position, + * if index is given. The string will be restored to the exact original + * content only when this class is destroyed. + * \param idx if this parameter is given, this class becomes an in-place + * substring of the original string. Otherwise, this class will store the + * entire string passed. In this case, the original string will not be + * modified. + * \param off if this parameter is given, the first off characters of the + * string are skipped. Note that idx calculations take place before + * offset computation, so idx is relative to the original string. + */ + explicit StringPart(char *s, size_t idx=std::string::npos, size_t off=0); + + /** + * Constructor from const C string + * \param s original string. A pointer to the string is taken, the string + * is NOT copied. Therefore, the caller is responsible to guarantee the + * string won't be deallocated while this class is alive. Note that this + * constructor misses the idx and off parameters as it's not possible to + * make an in-place substring of a string if it's const. + */ + explicit StringPart(const char *s); + + /** + * Substring constructor. Given a StringPart, produce another StringPart + * holding a substring of the original StringPart. Unlike the normal copy + * constructor that does deep copy, this one does shallow copy, and + * therefore the newly created object will share the same string pointer + * as rhs. Useful for making substrings of a substring without memory + * allocation. + * \param rhs a StringPart + */ + StringPart(StringPart& rhs, size_t idx, size_t off=0); + + /** + * Copy constructor. Note that deep copying is used, so that the newly + * created StringPart is a self-contained string. It has been done like + * that to be able to store the paths of mounted filesystems in an std::map + * \param rhs a StringPart + */ + StringPart(const StringPart& rhs); + + /** + * Operator = Note that deep copying is used, so that the assigned + * StringPart becomes a self-contained string. It has been done like + * that to be able to store the paths of mounted filesystems in an std::map + * \param rhs a StringPart + */ + StringPart& operator= (const StringPart& rhs); + + /** + * Compare two StringParts for inequality + * \param rhs second StringPart to compare to + * \return true if *this < rhs + */ + bool operator<(const StringPart& rhs) const + { + return strcmp(this->c_str(),rhs.c_str())<0; + } + + /** + * \param rhs a StringPart + * \return true if this starts with rhs + */ + bool startsWith(const StringPart& rhs) const; + + /** + * \param c char to find in the string, starting from the end + * \return the index of the last occurrence of c, or string::npos + */ + size_t findLastOf(char c) const; + + /** + * \return the StringPart length, which is the same as strlen(this->c_str()) + */ + size_t length() const { return index-offset; } + + /** + * \return the StringPart as a C string + */ + const char *c_str() const; + + /** + * \param index index into the string + * \return the equivalent of this->c_str()[index] + */ + char operator[] (size_t index) const; + + /** + * \return true if the string is empty + */ + bool empty() const { return length()==0; } + + /** + * Make this an empty string + */ + void clear(); + + /** + * Destructor + */ + ~StringPart() { clear(); } + +private: + /** + * To implement copy constructor and operator=. *this must be empty. + * \param rhs other StringPart that is assigned to *this. + */ + void assign(const StringPart& rhs); + + union { + std::string *str; ///< Pointer to underlying C++ string + char *cstr; ///< Pointer to underlying C string + const char *ccstr; ///< Pointer to underlying const C string + }; + size_t index; ///< Index into the character substituted by '\0' + size_t offset; ///< Offset to skip the first part of a string + char saved; ///< Char that was replaced by '\0' + bool owner; ///< True if this class owns str + char type; ///< either CPPSTR or CSTR. Using char to reduce size + enum { CPPSTR, CSTR, CCSTR }; ///< Possible values fot type +}; + +} //namespace miosix + +#endif //STRINGPART_H diff --git a/lib/miosix-kernel/miosix/interfaces/arch_registers.h b/lib/miosix-kernel/miosix/interfaces/arch_registers.h new file mode 100644 index 00000000..2dce6954 --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/arch_registers.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ARCH_REGISTERS_H +#define ARCH_REGISTERS_H + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file arch_registers.h + * This file should contain the list of hardware registers of the selected + * architecture, to allow application to directly access the hardware. + * + * The list of these registers is usually provided by the chip vendor in the + * form of one or more header files. + * + * To include these registers in a portable way, here we only include + * arch_registers_impl.h, which will be an header file in + * arch/arch name/board name/interfaces_impl + * + * The usual implementation of arch_registers_impl.h is simply to include + * the header files provided by the chip vendor. + */ + +/** + * \} + */ + +#include "boot/arch_registers_impl.h" + +#endif //ARCH_REGISTERS_H diff --git a/lib/miosix-kernel/miosix/interfaces/atomic_ops.h b/lib/miosix-kernel/miosix/interfaces/atomic_ops.h new file mode 100644 index 00000000..b974fc69 --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/atomic_ops.h @@ -0,0 +1,169 @@ +/*************************************************************************** + * Copyright (C) 2013, 2014, 2015 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ATOMIC_OPS_H +#define ATOMIC_OPS_H + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file atomic_ops.h + * This file contains various atomic operations useful for implementing + * lock-free algorithms. + * + * For architectures without hardware support for these operations, they are + * emulated by disabling interrupts. Note that these functions should be safe + * to be called also with interrupts disabled, so implementations that disable + * interrupts should be careful not to accidentally re-enable them if these + * functions are called with interupts disabled. + */ + +namespace miosix { + +/** + * Store a value in one memory location, and atomically read back the + * previously stored value. Performs atomically the following operation: + * + * \code + * inline int atomicSwap(volatile int *p, int v) + * { + * int result=*p; + * *p=v; + * return result; + * } + * \endcode + * + * \param p pointer to memory location where the atomic swap will take place + * \param v new value to be stored in *p + * \return the previous value of *p + */ +inline int atomicSwap(volatile int *p, int v); + +/** + * Atomically read the content of a memory location, add a number to the loaded + * value, and store the result. Performs atomically the following operation: + * + * \code + * inline void atomicAdd(volatile int *p, int incr) + * { + * *p+=incr; + * } + * \endcode + * + * \param p pointer to memory location where the atomic add will take place + * \param incr value to be added to *p + */ +inline void atomicAdd(volatile int *p, int incr); + +/** + * Atomically read the content of a memory location, add a number to the loaded + * value, store the result and return the previous value stored. + * Performs atomically the following operation: + * + * \code + * inline int atomicAddExchange(volatile int *p, int incr) + * { + * int result=*p; + * *p+=incr; + * return result; + * } + * \endcode + * + * \param pointer to memory location where the atomic add will take place + * \param incr value to be added to *p + * \return the previous value of *p + */ +inline int atomicAddExchange(volatile int *p, int incr); + +/** + * Atomically read the value of a memory location, and store a new value in it + * if it matches a given value. Also, return the previously stored value. + * Performs atomically the following operation: + * + * \code + * inline int atomicCompareAndSwap(volatile int *p, int prev, int next) + * { + * int result=*p; + * if(*p==prev) *p=next; + * return result; + * } + * \endcode + * + * \param p pointer to the memory location to compare and swap + * \param prev value to be compared against the content of *p + * \param next value to be stored in *p if *p==prev + * \return the value actually read from *p + */ +inline int atomicCompareAndSwap(volatile int *p, int prev, int next); + +/** + * An implementation of atomicFetchAndIncrement, as described in + * http://www.drdobbs.com/atomic-reference-counting-pointers/184401888 + * Atomically read a pointer stored in one memory loaction, and add + * a constant to a memory loaction placed at a given offset from the + * pointer. Performs atomically the following operation: + * + * \code + * void *atomicFetchAndIncrement(void * const volatile *p, int offset, int incr) + * { + * int *result=*p; + * if(result==0) return 0; + * *(result+offset)+=incr; + * return result; + * } + * \endcode + * + * \param p pointer to a const volatile pointer to object. While p is not + * subject to thread contention, *p is. + * \param offset the memory location to increment is **p+offset*sizeof(int) + * \param incr value to be added to **p+offset*sizeof(int) + * \return *p + */ +inline void *atomicFetchAndIncrement(void * const volatile * p, int offset, + int incr); + +} //namespace miosix + +/** + * \} + */ + +#ifdef _ARCH_ARM7_LPC2000 +#include "core/atomic_ops_impl_arm7.h" +#elif defined(_ARCH_CORTEXM3) || defined(_ARCH_CORTEXM4) \ + || defined(_ARCH_CORTEXM7) +#include "core/atomic_ops_impl_cortexMx.h" +#elif defined(_ARCH_CORTEXM0) +#include "core/atomic_ops_impl_cortexM0.h" +#else +#error "No atomic ops for this architecture" +#endif + +#endif //ATOMIC_OPS_H diff --git a/lib/miosix-kernel/miosix/interfaces/bsp.h b/lib/miosix-kernel/miosix/interfaces/bsp.h new file mode 100644 index 00000000..641db2e6 --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/bsp.h @@ -0,0 +1,108 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef BSP_H +#define BSP_H + +namespace miosix { + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file bsp.h + * This file contains architecture specific board support package. + * It must at least provide these four functions: + * + * IRQbspInit(), to initialize the board to a known state early in the boot + * process (before the kernel is started, and when interrupts are disabled) + * + * bspInit2(), to perform the remaining part of the initialization, once the + * kernel is started + * + * shutdown(), for system shutdown. This function is called in case main() + * returns, and is available to be called by user code. + * + * reboot(), a function that can be called to reboot the system under normal + * (non error) conditions. It should sync and unmount the filesystem, and + * perform a reboot. This function is available for user code. + * + * Other than this, the board support package might contain other functions, + * classes, macros etc. to support peripherals and or board hardware. + */ + +/** + * \internal + * Initializes the I/O pins, and put system peripherals to a known state.
+ * Must be called before starting kernel. Interrupts must be disabled.
+ * This function is used by the kernel and should not be called by user code. + */ +void IRQbspInit(); + +/** + * \internal + * Performs the part of initialization that must be done after the kernel is + * started.
+ * This function is used by the kernel and should not be called by user code. + */ +void bspInit2(); + +/** + * This function disables filesystem (if enabled), serial port (if enabled) and + * shuts down the system, usually by putting the procesor in a deep sleep + * state.
+ * The action to start a new boot is system-specific, can be for example a + * reset, powercycle or a special GPIO configured to wakeup the processor from + * deep sleep.
+ * This function does not return.
+ * WARNING: close all files before using this function, since it unmounts the + * filesystem.
+ */ +void shutdown(); + +/** + * The difference between this function and miosix_private::IRQsystemReboot() + * is that this function disables filesystem (if enabled), serial port + * (if enabled) while miosix_private::system_reboot() does not do all these + * things. miosix_private::IRQsystemReboot() is designed to reboot the system + * when an unrecoverable error occurs, and is used primarily in kernel code, + * reboot() is designed to reboot the system in normal conditions.
+ * This function does not return.
+ * WARNING: close all files before using this function, since it unmounts the + * filesystem. + */ +void reboot(); + +/** + * \} + */ + +} //namespace miosix + +#endif //BSP_H diff --git a/lib/miosix-kernel/miosix/interfaces/delays.h b/lib/miosix-kernel/miosix/interfaces/delays.h new file mode 100644 index 00000000..afe4d2ee --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/delays.h @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef DELAYS_H +#define DELAYS_H + +namespace miosix { + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file delays.h + * This file contains two functions, delayMs() and delayUs() which implement + * busy wait delays. + */ + +/** + * Delay function. Accuracy depends on the underlying implementation which is + * architecture specific.
+ * Delay time can be inaccurate if interrupts are enabled or the kernel is + * running due to time spent in interrupts and due to preemption.
+ * It is implemented using busy wait, so can be safely used even when the + * kernel is paused or interrupts are disabled.
+ * If the kernel is running it is *highly* recomended to use Thread::sleep since + * it gives CPU time to other threads and/or it puts the CPU in low power mode. + * \param mseconds milliseconds to wait + */ +void delayMs(unsigned int mseconds); + +/** + * Delay function. Accuracy depends on the underlying implementation which is + * architecture specific.
+ * Delay time can be inaccurate if interrupts are enabled or the kernel is + * running due to time spent in interrupts and due to preemption.
+ * It is implemented using busy wait, so can be safely used even when the + * kernel is paused or interrupts are disabled.
+ * \param useconds microseconds to wait. Only values between 1 and 1000 are + * allowed. For greater delays use Thread::sleep() or delayMs(). + */ +void delayUs(unsigned int useconds); + +/** + * \} + */ + +} + +#endif //DELAYS_H diff --git a/lib/miosix-kernel/miosix/interfaces/endianness.h b/lib/miosix-kernel/miosix/interfaces/endianness.h new file mode 100644 index 00000000..38aac009 --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/endianness.h @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (C) 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifdef _ARCH_ARM7_LPC2000 +#include "core/endianness_impl_arm7.h" +#elif defined(_ARCH_CORTEXM0) || defined(_ARCH_CORTEXM3) \ + || defined(_ARCH_CORTEXM4) || defined(_ARCH_CORTEXM7) +#include "core/endianness_impl_cortexMx.h" +#else +#error "No endianness code for this architecture" +#endif + +#ifndef ENDIANNESS_H +#define ENDIANNESS_H + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file endianness.h + * This file contains optimized functions to convert data from the system's + * endianness to little or big endian, as well as to perform byte swapping. + */ + +// Implementation of these functions is in endianness_impl.h + +#ifdef __cplusplus +#define __MIOSIX_INLINE inline +#else //__cplusplus +#define __MIOSIX_INLINE static inline +#endif //__cplusplus + +/** + * \fn inline unsigned short swapBytes16(unsigned short x) + * \param x an short int + * \return the same short with its bytes swapped + */ +__MIOSIX_INLINE unsigned short swapBytes16(unsigned short x); + +/** + * \fn inline unsigned int swapBytes32(unsigned int x) + * \param x an int + * \return the same int with its bytes swapped + */ +__MIOSIX_INLINE unsigned int swapBytes32(unsigned int x); + +/** + * \fn inline unsigned long long swapBytes64(unsigned long long x) + * \param x a long long + * \return the same long long with its bytes swapped + */ +__MIOSIX_INLINE unsigned long long swapBytes64(unsigned long long x); + +#undef __MIOSIX_INLINE + +/** + * \def toLittleEndian16(x) + * Convert a (signed or unsigned) short int from the system representation + * to little endian + * \param x value to convert + * \return value converted to little endian + * + * \def toLittleEndian32(x) + * Convert a (signed or unsigned) int from the system representation + * to little endian + * \param x value to convert + * \return value converted to little endian + * + * \def toLittleEndian64(x) + * Convert a (signed or unsigned) long long from the system representation + * to little endian + * \param x value to convert + * \return value converted to little endian + * + * \def toBigEndian16(x) + * Convert a (signed or unsigned) short int from the system representation + * to big endian + * \param x value to convert + * \return value converted to big endian + * + * \def toBigEndian32(x) + * Convert a (signed or unsigned) int from the system representation + * to big endian + * \param x value to convert + * \return value converted to big endian + * + * \def toBigEndian64(x) + * Convert a (signed or unsigned) long long from the system representation + * to big endian + * \param x value to convert + * \return value converted to big endian + */ + +#ifdef MIOSIX_LITTLE_ENDIAN +#define toLittleEndian16(x) (x) +#define toLittleEndian32(x) (x) +#define toLittleEndian64(x) (x) +#define fromLittleEndian16(x) (x) +#define fromLittleEndian32(x) (x) +#define fromLittleEndian64(x) (x) +#define toBigEndian16(x) swapBytes16(x) +#define toBigEndian32(x) swapBytes32(x) +#define toBigEndian64(x) swapBytes64(x) +#define fromBigEndian16(x) swapBytes16(x) +#define fromBigEndian32(x) swapBytes32(x) +#define fromBigEndian64(x) swapBytes64(x) +#elif defined(MIOSIX_BIG_ENDIAN) +#define toLittleEndian16(x) swapBytes16(x) +#define toLittleEndian32(x) swapBytes32(x) +#define toLittleEndian64(x) swapBytes64(x) +#define fromLittleEndian16(x) swapBytes16(x) +#define fromLittleEndian32(x) swapBytes32(x) +#define fromLittleEndian64(x) swapBytes64(x) +#define toBigEndian16(x) (x) +#define toBigEndian32(x) (x) +#define toBigEndian64(x) (x) +#define fromBigEndian16(x) (x) +#define fromBigEndian32(x) (x) +#define fromBigEndian64(x) (x) +#else +#error "endianness_impl.h does not define endianness" +#endif + +/** + * \} + */ + +#endif //ENDIANNESS_H diff --git a/lib/miosix-kernel/miosix/interfaces/gpio.h b/lib/miosix-kernel/miosix/interfaces/gpio.h new file mode 100644 index 00000000..3cd9857a --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/gpio.h @@ -0,0 +1,144 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef GPIO_H +#define GPIO_H + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file gpio.h + * The interface to gpios provided by Miosix is in the form of templates, + * therefore this file can only include gpio_impl.h with the architecture + * dependand code. + * + * The interface should be as follows: + * First a class Mode containing an enum Mode_ needs to be defined. Its minimum + * implementation is this: + * \code + * class Mode + * { + * public: + * enum Mode_ + * { + * INPUT, + * OUTPUT + * }; + * private: + * Mode(); //Just a wrapper class, disallow creating instances + * }; + * \endcode + * + * This class should define the possible configurations of a gpio pin. + * The minimum required is INPUT and OUTPUT, but this can be extended to other + * options to reflect the hardware capabilities of gpios. For example, if + * gpios can be set as input with pull up, it is possible to add INPUT_PULL_UP + * to the enum. + * + * Then a template Gpio class should be provided, with at least the following + * member functions: + * \code + * template + * class Gpio + * { + * public: + * static void mode(Mode::Mode_ m); + * static void high(); + * static void low(); + * static int value(); + * unsigned int getPort() const; + * unsigned char getNumber() const; + * private: + * Gpio();//Only static member functions, disallow creating instances + * }; + * \endcode + * + * mode() should take a Mode::Mode_ enum and set the mode of the gpio + * (input, output or other architecture specific) + * + * high() should set a gpio configured as output to high logic level + * + * low() should set a gpio configured as output to low logic level + * + * value() should return either 1 or 0 to refect the state of a gpio configured + * as input + * + * getPort() should return the gpio port + * + * getNumber() should return the gpio pin number + * + * Lastly, a number of constants must be provided to be passed as first template + * parameter of the Gpio class, usually identifying the gpio port, while the + * second template parameter should be used to specify a gpio pin within a port. + * + * The intended use is this: + * considering an architecture with two ports, PORTA and PORTB each with 8 pins. + * The header gpio_impl.h should provide two constants, for example named + * GPIOA_BASE and GPIOB_BASE. + * + * The user can declare the hardware mapping between gpios and what is connected + * to them, usually in an header file. If for example PORTA.0 is connected to + * a button while PORTB.4 to a led, the header file might contain: + * + * \code + * typedef Gpio button; + * typedef Gpio led; + * \endcode + * + * This allows the rest of the code to be written in terms of leds and buttons, + * without a reference to which pin they are connected to, something that might + * change. + * + * A simple code using these gpios could be: + * \code + * void blinkUntilButtonPressed() + * { + * led::mode(Mode::OUTPUT); + * button::mode(Mode::INPUT); + * for(;;) + * { + * if(button::value()==1) break; + * led::high(); + * Thread::sleep(250); + * led::low(); + * Thread::sleep(250); + * } + * } + * \endcode + * + */ + +/** + * \} + */ + +#include "interfaces-impl/gpio_impl.h" + +#endif //GPIO_H diff --git a/lib/miosix-kernel/miosix/interfaces/portability.h b/lib/miosix-kernel/miosix/interfaces/portability.h new file mode 100644 index 00000000..a32cb6e2 --- /dev/null +++ b/lib/miosix-kernel/miosix/interfaces/portability.h @@ -0,0 +1,195 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef PORTABILITY_H +#define PORTABILITY_H + +//For SCHED_TYPE_* config options +#include "config/miosix_settings.h" +#include + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \file portability.h + * This file is the interface from the Miosix kernel to the hardware. + * It ccontains what is required to perform a context switch, disable + * interrupts, set up the stack frame and registers of a newly created thread, + * and contains iterrupt handlers for preemption and yield. + * + * Since some of the functions in this file must be inline for speed reasons, + * and context switch code must be macros, at the end of this file the file + * portability_impl.h is included. + * This file should contain the implementation of those inline functions. + */ + +/** + * \} + */ + +/** + * \namespace miosix_pivate + * contains architecture-specific functions. These functions are separated from + * the functions in kernel.h because:
+ * - to port the kernel to another processor you only need to rewrite these + * functions. + * - these functions are only useful for writing hardare drivers, most user code + * does not need them. + */ +namespace miosix_private { + +/** + * \addtogroup Interfaces + * \{ + */ + +/** + * \internal + * Used after an unrecoverable error condition to restart the system, even from + * within an interrupt routine. + */ +void IRQsystemReboot(); + +/** + * \internal + * Cause a context switch. + * It is used by the kernel, and should not be used by end users. + */ +inline void doYield(); + +/** + * \internal + * Initializes a ctxsave array when a thread is created. + * It is used by the kernel, and should not be used by end users. + * \param ctxsave a pointer to a field ctxsave inside a Thread class that need + * to be filled + * \param pc starting program counter of newly created thread, used to + * initialize ctxsave + * \param sp starting stack pointer of newly created thread, used to initialize + * ctxsave + * \param argv starting data passed to newly created thread, used to initialize + * ctxsave + */ +void initCtxsave(unsigned int *ctxsave, void *(*pc)(void *), unsigned int *sp, + void *argv); + +/** + * \internal + * Used before every context switch to check if the stack of the thread has + * overflowed must be called before IRQfindNextThread(). + */ +void IRQstackOverflowCheck(); + +/** + * \internal + * Called by miosix::start_kernel to handle the architecture-specific part of + * initialization. It is used by the kernel, and should not be used by end users + */ +void IRQportableStartKernel(); + +/** + * \internal + * This function disables interrupts. + * This is used by the kernel to implement disableInterrupts() and + * enableInterrupts(). You should never need to call these functions directly. + */ +inline void doDisableInterrupts(); + +/** + * \internal + * This function enables interrupts. + * This is used by the kernel to implement disableInterrupts() and + * enableInterrupts(). You should never need to call these functions directly. + */ +inline void doEnableInterrupts(); + +/** + * \internal + * This is used by the kernel to implement areInterruptsEnabled() + * You should never need to call this function directly. + * \return true if interrupts are enabled + */ +inline bool checkAreInterruptsEnabled(); + +/** + * \internal + * used by the idle thread to put cpu in low power mode + */ +void sleepCpu(); + +#ifdef SCHED_TYPE_CONTROL_BASED +/** + * Allow access to a second timer to allow variable burst preemption together + * with fixed tick timekeeping. + */ +class AuxiliaryTimer +{ +public: + /** + * Initializes the auxiliary timer. + */ + static void IRQinit(); + + /** + * \internal + * Used to implement new control-based scheduler. Used to get the error + * between desired burst time and real burst time. + * It is responsibility of who implements this function to ensure that + * the returned value has the three most significant bits set to zero + * (that is, is a positive number between 0 and 0x1fffffff). This is + * necessary to avoid oerflow in the scheduler implementation. + * \return time since last time set_timer_value() was called. + */ + static int IRQgetValue(); + + /** + * \internal + * Reset timer counter to zero and set next preempt after x timer counts. + * \param x time before next preempt will occur. Must be >0 + */ + static void IRQsetValue(int x); + +private: + //Unwanted functions + AuxiliaryTimer(); + AuxiliaryTimer& operator= (AuxiliaryTimer& ); +}; +#endif //SCHED_TYPE_CONTROL_BASED + +/** + * \} + */ + +} //namespace miosix_private + +// This contains the macros and the implementation of inline functions +#include "portability_impl.h" + +#endif //PORTABILITY_H diff --git a/lib/miosix-kernel/miosix/kernel/IRQDisplayPrint.cpp b/lib/miosix-kernel/miosix/kernel/IRQDisplayPrint.cpp new file mode 100644 index 00000000..0140cfc5 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/IRQDisplayPrint.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + * Copyright (C) 2016 by Lorenzo Pinosa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "IRQDisplayPrint.h" +#include "string" +#include "display.h" +#include + +using namespace std; +using namespace mxgui; + +namespace miosix { + +IRQDisplayPrint::IRQDisplayPrint() + : Device(TTY), right_margin(5), bottom_margin(5), + carriage_return_enabled(false) +{ +} + +IRQDisplayPrint::~IRQDisplayPrint() {} + +void IRQDisplayPrint::IRQwrite(const char * to_print) +{ + string str = to_print; + input_queue.put(str); +} + +ssize_t IRQDisplayPrint::writeBlock(const void * buffer, size_t size, off_t where) +{ + string str = reinterpret_cast(buffer); + str = str.substr(where, size); + input_queue.put(str); + return size; +} + +void IRQDisplayPrint::printIRQ() +{ + while (true) + { + string tmp; + input_queue.get(tmp); + if (tmp.size() == 0) + continue; + if (carriage_return_fix(tmp)) + continue; + process_string(tmp); + check_array_overflow(); + internal_print(); + } +} + +//returns true if the current string has to be discarded +bool IRQDisplayPrint::carriage_return_fix(string str) +{ + if (!carriage_return_enabled && str.compare("\r\n") == 0) + { + carriage_return_enabled = true; + return true; + } + if (str.compare("\r\n") == 0) + { + return false; + } + else + { + carriage_return_enabled = false; + return false; + } +} + +void IRQDisplayPrint::check_array_overflow() +{ + int font_height, display_h; + { + DrawingContext dc(Display::instance()); + font_height = dc.getFont().getHeight(); + display_h = dc.getHeight(); + } + while (font_height * print_lines.size() >= display_h - bottom_margin) + { + print_lines.erase(print_lines.begin()); + } +} + +void IRQDisplayPrint::process_string(string str) +{ + int display_w; + auto_ptr font; + { + DrawingContext dc(Display::instance()); + display_w = dc.getWidth(); + font = auto_ptr(new Font(dc.getFont())); + } + + vector lines; + while (str.length() > 0) + { + int lenght = font->calculateLength(str.c_str()); + if (lenght < display_w) + { + lines.push_back(str); + break; + } + //else + int len = 0; + string str_part; + for (int i = 1; len < display_w - right_margin; i++) + { + str_part = str.substr(0, i); + len = font->calculateLength(str_part.c_str()); + } + lines.push_back(str_part); + str = str.substr(str_part.length()); + } + + + for (vector::iterator it = lines.begin(); it != lines.end(); ++it) + { + print_lines.push_back(*it); + } +} + +void IRQDisplayPrint::internal_print() +{ + DrawingContext dc(Display::instance()); + //dc.clear(0xFFF); + int cur_y = 0; + + for (vector::iterator it = print_lines.begin(); it != print_lines.end(); ++it) + { + string str = *it; + dc.write(Point(0, cur_y), str.c_str()); + Point up_left(dc.getFont().calculateLength(str.c_str()), cur_y); + Point down_right(dc.getWidth() -1, cur_y + dc.getFont().getHeight()); + dc.clear(up_left, down_right, 0x0); + cur_y += dc.getFont().getHeight(); + } +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/kernel/IRQDisplayPrint.h b/lib/miosix-kernel/miosix/kernel/IRQDisplayPrint.h new file mode 100644 index 00000000..1d518948 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/IRQDisplayPrint.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2016 by Lorenzo Pinosa * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "../filesystem/devfs/devfs.h" +#include "queue.h" +#include +#include "kernel.h" + +using namespace std; + +namespace miosix { + +class IRQDisplayPrint : public Device +{ +public: + IRQDisplayPrint(); + ~IRQDisplayPrint(); + + void IRQwrite(const char *str); + ssize_t writeBlock(const void *buffer, size_t size, off_t where); + + void printIRQ(); +private: + Queue input_queue; + vector print_lines; + + int right_margin; + int bottom_margin; + bool carriage_return_enabled; + + bool carriage_return_fix(string str); + void process_string(string str); + void check_array_overflow(); + void internal_print(); +}; + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/kernel/SystemMap.cpp b/lib/miosix-kernel/miosix/kernel/SystemMap.cpp new file mode 100644 index 00000000..e45eb34c --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/SystemMap.cpp @@ -0,0 +1,49 @@ + +#include "SystemMap.h" + +using namespace std; + +#ifdef WITH_PROCESSES + +namespace miosix { + +SystemMap& SystemMap::instance() +{ + static SystemMap singleton; + return singleton; +} + +void SystemMap::addElfProgram(const char* name, const unsigned int *elf, unsigned int size) +{ + string sName(name); + if(mPrograms.find(sName) == mPrograms.end()) + mPrograms.insert(make_pair(sName, make_pair(elf, size))); +} + +void SystemMap::removeElfProgram(const char* name) +{ + string sName(name); + ProgramsMap::iterator it = mPrograms.find(sName); + + if(it != mPrograms.end()) + mPrograms.erase(it); +} + +pair SystemMap::getElfProgram(const char* name) const +{ + ProgramsMap::const_iterator it = mPrograms.find(string(name)); + + if(it == mPrograms.end()) + return make_pair(0, 0); + + return it->second; +} + +unsigned int SystemMap::getElfCount() const +{ + return mPrograms.size(); +} + +} //namespace miosix + +#endif //WITH_PROCESSES diff --git a/lib/miosix-kernel/miosix/kernel/SystemMap.h b/lib/miosix-kernel/miosix/kernel/SystemMap.h new file mode 100644 index 00000000..7094420d --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/SystemMap.h @@ -0,0 +1,38 @@ +#ifndef SYSTEMMAP_H +#define SYSTEMMAP_H + +#include "kernel/sync.h" +#include "config/miosix_settings.h" + +#include +#include + +#ifdef WITH_PROCESSES + +namespace miosix { + +class SystemMap +{ +public: + static SystemMap &instance(); + + void addElfProgram(const char *name, const unsigned int *elf, unsigned int size); + void removeElfProgram(const char *name); + std::pair getElfProgram(const char *name) const; + + unsigned int getElfCount() const; + +private: + SystemMap() {} + SystemMap(const SystemMap&); + SystemMap& operator= (const SystemMap&); + + typedef std::map > ProgramsMap; + ProgramsMap mPrograms; +}; + +} //namespace miosix + +#endif //WITH_PROCESSES + +#endif /* SYSTEMMAP_H */ diff --git a/lib/miosix-kernel/miosix/kernel/elf_program.cpp b/lib/miosix-kernel/miosix/kernel/elf_program.cpp new file mode 100644 index 00000000..9edfd49a --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/elf_program.cpp @@ -0,0 +1,348 @@ +/*************************************************************************** + * Copyright (C) 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "elf_program.h" +#include "process_pool.h" +#include +#include +#include + +using namespace std; + +#ifdef WITH_PROCESSES + +namespace miosix { + +///\internal Enable/disable debugging of program loading +//#define DBG iprintf +#define DBG(x,...) do {} while(0) + +///By convention, in an elf file for Miosix, the data segment starts @ this addr +static const unsigned int DATA_BASE=0x40000000; + +// +// class ElfProgram +// + +ElfProgram::ElfProgram(const unsigned int *elf, unsigned int size) + : elf(elf), size(size) +{ + //Trying to follow the "full recognition before processing" approach, + //(http://www.cs.dartmouth.edu/~sergey/langsec/occupy/FullRecognition.jpg) + //all of the elf fields that will later be used are checked in advance. + //Unused fields are unchecked, so when using new fields, add new checks + if(validateHeader()==false) throw runtime_error("Bad file"); +} + +bool ElfProgram::validateHeader() +{ + //Validate ELF header + //Note: this code assumes a little endian elf and a little endian ARM CPU + if(isUnaligned8(getElfBase())) + throw runtime_error("Elf file load address alignment error"); + if(sizee_ident,magic,EI_NIDENT)) + throw runtime_error("Unrecognized format"); + if(ehdr->e_type!=ET_EXEC) throw runtime_error("Not an executable"); + if(ehdr->e_machine!=EM_ARM) throw runtime_error("Wrong CPU arch"); + if(ehdr->e_version!=EV_CURRENT) return false; + if(ehdr->e_entry>=size) return false; + if(ehdr->e_phoff>=size-sizeof(Elf32_Phdr)) return false; + if(isUnaligned4(ehdr->e_phoff)) return false; + // Old GCC 4.7.3 used to set bit 0x2 (EF_ARM_HASENTRY) but there's no trace + // of this requirement in the current ELF spec for ARM. + if((ehdr->e_flags & EF_ARM_EABIMASK) != EF_ARM_EABI_VER5) return false; + #if !defined(__FPU_USED) || __FPU_USED==0 + if(ehdr->e_flags & EF_ARM_VFP_FLOAT) throw runtime_error("FPU required"); + #endif + if(ehdr->e_ehsize!=sizeof(Elf32_Ehdr)) return false; + if(ehdr->e_phentsize!=sizeof(Elf32_Phdr)) return false; + //This to avoid that the next condition could pass due to 32bit wraparound + //20 is an arbitrary number, could be increased if required + if(ehdr->e_phnum>20) throw runtime_error("Too many segments"); + if(ehdr->e_phoff+(ehdr->e_phnum*sizeof(Elf32_Phdr))>size) return false; + + //Validate program header table + bool codeSegmentPresent=false; + bool dataSegmentPresent=false; + bool dynamicSegmentPresent=false; + int dataSegmentSize=0; + const Elf32_Phdr *phdr=getProgramHeaderTable(); + for(int i=0;ip_offset>=size) return false; + if(phdr->p_filesz>=size) return false; + if(phdr->p_offset+phdr->p_filesz>size) return false; + switch(phdr->p_align) + { + case 1: break; + case 4: + if(isUnaligned4(phdr->p_offset)) return false; + break; + case 8: + if(isUnaligned8(phdr->p_offset)) return false; + break; + default: + throw runtime_error("Unsupported segment alignment"); + } + + switch(phdr->p_type) + { + case PT_LOAD: + if(phdr->p_flags & ~(PF_R | PF_W | PF_X)) return false; + if(!(phdr->p_flags & PF_R)) return false; + if((phdr->p_flags & PF_W) && (phdr->p_flags & PF_X)) + throw runtime_error("File violates W^X"); + if(phdr->p_flags & PF_X) + { + if(codeSegmentPresent) return false; //Can't apper twice + codeSegmentPresent=true; + if(ehdr->e_entryp_offset || + ehdr->e_entry>phdr->p_offset+phdr->p_filesz || + phdr->p_filesz!=phdr->p_memsz) return false; + } + if((phdr->p_flags & PF_W) && !(phdr->p_flags & PF_X)) + { + if(dataSegmentPresent) return false; //Two data segments? + dataSegmentPresent=true; + if(phdr->p_memszp_filesz) return false; + unsigned int maxSize=MAX_PROCESS_IMAGE_SIZE- + MIN_PROCESS_STACK_SIZE; + if(phdr->p_memsz>=maxSize) + throw runtime_error("Data segment too big"); + dataSegmentSize=phdr->p_memsz; + } + break; + case PT_DYNAMIC: + if(dynamicSegmentPresent) return false; //Two dynamic segments? + dynamicSegmentPresent=true; + //DYNAMIC segment *must* come after data segment + if(dataSegmentPresent==false) return false; + if(phdr->p_align<4) return false; + if(validateDynamicSegment(phdr,dataSegmentSize)==false) + return false; + break; + default: + //Ignoring other segments + break; + } + } + if(codeSegmentPresent==false) return false; //Can't not have code segment + return true; +} + +bool ElfProgram::validateDynamicSegment(const Elf32_Phdr *dynamic, + unsigned int dataSegmentSize) +{ + unsigned int base=getElfBase(); + const Elf32_Dyn *dyn=reinterpret_cast(base+dynamic->p_offset); + const int dynSize=dynamic->p_memsz/sizeof(Elf32_Dyn); + Elf32_Addr dtRel=0; + Elf32_Word dtRelsz=0; + unsigned int hasRelocs=0; + bool miosixTagFound=false; + unsigned int ramSize=0; + unsigned int stackSize=0; + for(int i=0;id_tag) + { + case DT_REL: + hasRelocs |= 0x1; + dtRel=dyn->d_un.d_ptr; + break; + case DT_RELSZ: + hasRelocs |= 0x2; + dtRelsz=dyn->d_un.d_val; + break; + case DT_RELENT: + hasRelocs |= 0x4; + if(dyn->d_un.d_val!=sizeof(Elf32_Rel)) return false; + break; + case DT_MX_ABI: + if(dyn->d_un.d_val==DV_MX_ABI_V1) miosixTagFound=true; + else throw runtime_error("Unknown/unsupported DT_MX_ABI"); + break; + case DT_MX_RAMSIZE: + ramSize=dyn->d_un.d_val; + break; + case DT_MX_STACKSIZE: + stackSize=dyn->d_un.d_val; + break; + case DT_RELA: + case DT_RELASZ: + case DT_RELAENT: + throw runtime_error("RELA relocations unsupported"); + default: + //Ignore other entries + break; + } + } + if(miosixTagFound==false) throw runtime_error("Not a Miosix executable"); + if(stackSizeMAX_PROCESS_IMAGE_SIZE) + throw runtime_error("Requested image size is too large"); + if((stackSize & 0x3) || + (ramSize & 0x3) || + (ramSize < ProcessPool::blockSize) || + (stackSize>MAX_PROCESS_IMAGE_SIZE) || + (dataSegmentSize>MAX_PROCESS_IMAGE_SIZE) || + (dataSegmentSize+stackSize>ramSize)) + throw runtime_error("Invalid stack or RAM size"); + + if(hasRelocs!=0 && hasRelocs!=0x7) return false; + if(hasRelocs) + { + //The third condition does not imply the other due to 32bit wraparound + if(dtRel>=size) return false; + if(dtRelsz>=size) return false; + if(dtRel+dtRelsz>size) return false; + if(isUnaligned4(dtRel)) return false; + + const Elf32_Rel *rel=reinterpret_cast(base+dtRel); + const int relSize=dtRelsz/sizeof(Elf32_Rel); + for(int i=0;ir_info)) + { + case R_ARM_NONE: + break; + case R_ARM_RELATIVE: + if(rel->r_offsetr_offset>DATA_BASE+dataSegmentSize-4) return false; + if(rel->r_offset & 0x3) return false; + break; + default: + throw runtime_error("Unexpected relocation type"); + } + } + } + return true; +} + +// +// class ProcessImage +// + +void ProcessImage::load(const ElfProgram& program) +{ + if(image) ProcessPool::instance().deallocate(image); + const unsigned int base=program.getElfBase(); + const Elf32_Phdr *phdr=program.getProgramHeaderTable(); + const Elf32_Phdr *dataSegment=0; + Elf32_Addr dtRel=0; + Elf32_Word dtRelsz=0; + bool hasRelocs=false; + for(int i=0;ip_type) + { + case PT_LOAD: + if((phdr->p_flags & PF_W) && !(phdr->p_flags & PF_X)) + dataSegment=phdr; + break; + case PT_DYNAMIC: + { + const Elf32_Dyn *dyn=reinterpret_cast + (base+phdr->p_offset); + const int dynSize=phdr->p_memsz/sizeof(Elf32_Dyn); + for(int i=0;id_tag) + { + case DT_REL: + hasRelocs=true; + dtRel=dyn->d_un.d_ptr; + break; + case DT_RELSZ: + hasRelocs=true; + dtRelsz=dyn->d_un.d_val; + break; + case DT_MX_RAMSIZE: + size=dyn->d_un.d_val; + image=ProcessPool::instance() + .allocate(dyn->d_un.d_val); + default: + break; + } + } + break; + } + default: + //Ignoring other segments + break; + } + } + const char *dataSegmentInFile= + reinterpret_cast(base+dataSegment->p_offset); + char *dataSegmentInMem=reinterpret_cast(image); + memcpy(dataSegmentInMem,dataSegmentInFile,dataSegment->p_filesz); + dataSegmentInMem+=dataSegment->p_filesz; + memset(dataSegmentInMem,0,dataSegment->p_memsz-dataSegment->p_filesz); + if(hasRelocs) + { + const Elf32_Rel *rel=reinterpret_cast(base+dtRel); + const int relSize=dtRelsz/sizeof(Elf32_Rel); + const unsigned int ramBase=reinterpret_cast(image); + DBG("Relocations -- start (code base @0x%x, data base @ 0x%x)\n",base,ramBase); + for(int i=0;ir_offset-DATA_BASE)/4; + switch(ELF32_R_TYPE(rel->r_info)) + { + case R_ARM_RELATIVE: + if(image[offset]>=DATA_BASE) + { + DBG("R_ARM_RELATIVE offset 0x%x from 0x%x to 0x%x\n", + offset*4,image[offset],image[offset]+ramBase-DATA_BASE); + image[offset]+=ramBase-DATA_BASE; + } else { + DBG("R_ARM_RELATIVE offset 0x%x from 0x%x to 0x%x\n", + offset*4,image[offset],image[offset]+base); + image[offset]+=base; + } + break; + default: + break; + } + } + DBG("Relocations -- end\n"); + } +} + +ProcessImage::~ProcessImage() +{ + if(image) ProcessPool::instance().deallocate(image); +} + +} //namespace miosix + +#endif //WITH_PROCESSES diff --git a/lib/miosix-kernel/miosix/kernel/elf_program.h b/lib/miosix-kernel/miosix/kernel/elf_program.h new file mode 100644 index 00000000..1bb8756e --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/elf_program.h @@ -0,0 +1,190 @@ +/*************************************************************************** + * Copyright (C) 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ELF_PROGRAM_H +#define ELF_PROGRAM_H + +#include +#include "elf_types.h" +#include "config/miosix_settings.h" + +#ifdef WITH_PROCESSES + +namespace miosix { + +/** + * This class represents an elf file. + */ +class ElfProgram +{ +public: + /** + * Constructor + * \param elf pointer to the elf file's content. Ownership of the data + * remains of the caller, that is, the pointer is not deleted by this + * class. This is done to allow passing a pointer directly to a location + * in the microcontroller's FLASH memory, in order to avoid copying the + * elf in RAM + * \param size size of the content of the elf file + */ + ElfProgram(const unsigned int *elf, unsigned int size); + + /** + * \return the a pointer to the elf header + */ + const Elf32_Ehdr *getElfHeader() const + { + return reinterpret_cast(elf); + } + + /** + * \return the already relocated value of the entry point + */ + unsigned int getEntryPoint() const + { + unsigned int base=reinterpret_cast(elf); + return base+getElfHeader()->e_entry; + } + + /** + * \return an array of struct Elf32_Phdr + */ + const Elf32_Phdr *getProgramHeaderTable() const + { + unsigned int base=reinterpret_cast(elf); + return reinterpret_cast(base+getElfHeader()->e_phoff); + } + + /** + * \return the number of entries in the program header table + */ + int getNumOfProgramHeaderEntries() const + { + return getElfHeader()->e_phnum; + } + + /** + * \return a number representing the elf base address in memory + */ + unsigned int getElfBase() const + { + return reinterpret_cast(elf); + } + + /** + * \return the size of the elf file, as passed in the class' constructor + */ + unsigned int getElfSize() const + { + return size; + } + +private: + /** + * \param size elf file size + * \return false if the file is not valid + * \throws runtime_error for selected specific types of errors + */ + bool validateHeader(); + + /** + * \param dynamic pointer to dynamic segment + * \param size elf file size + * \param dataSegmentSize size of data segment in memory + * \return false if the dynamic segment is not valid + * \throws runtime_error for selected specific types of errors + */ + bool validateDynamicSegment(const Elf32_Phdr *dynamic, + unsigned int dataSegmentSize); + + /** + * \param x field to check for word alignment issues + * \return true if not aligned correctly + */ + static bool isUnaligned4(unsigned int x) { return x & 0b11; } + + /** + * \param x field to check for doubleword alignment issues + * \return true if not aligned correctly + */ + static bool isUnaligned8(unsigned int x) { return x & 0b111; } + + const unsigned int * const elf; /// * + ***************************************************************************/ + +#ifndef ELF_TYPES_H +#define ELF_TYPES_H + +#include +#include "config/miosix_settings.h" + +#ifdef WITH_PROCESSES + +namespace miosix { + +// elf-specific types +typedef uint32_t Elf32_Word; +typedef int32_t Elf32_Sword; +typedef uint16_t Elf32_Half; +typedef uint32_t Elf32_Off; +typedef uint32_t Elf32_Addr; + +// Size of e_ident in the elf header +const int EI_NIDENT=16; + +/* + * Elf header + */ +struct Elf32_Ehdr +{ + unsigned char e_ident[EI_NIDENT]; // Ident bytes + Elf32_Half e_type; // File type, any of the ET_* constants + Elf32_Half e_machine; // Target machine + Elf32_Word e_version; // File version + Elf32_Addr e_entry; // Start address + Elf32_Off e_phoff; // Phdr file offset + Elf32_Off e_shoff; // Shdr file offset + Elf32_Word e_flags; // File flags + Elf32_Half e_ehsize; // Sizeof ehdr + Elf32_Half e_phentsize; // Sizeof phdr + Elf32_Half e_phnum; // Number phdrs + Elf32_Half e_shentsize; // Sizeof shdr + Elf32_Half e_shnum; // Number shdrs + Elf32_Half e_shstrndx; // Shdr string index +} __attribute__((packed)); + +// Values for e_type +const Elf32_Half ET_NONE = 0; // Unknown type +const Elf32_Half ET_REL = 1; // Relocatable +const Elf32_Half ET_EXEC = 2; // Executable +const Elf32_Half ET_DYN = 3; // Shared object +const Elf32_Half ET_CORE = 4; // Core file + +// Values for e_version +const Elf32_Word EV_CURRENT = 1; + +// Values for e_machine +const Elf32_Half EM_ARM = 0x28; + +// Values for e_flags +const Elf32_Word EF_ARM_EABIMASK = 0xff000000; +const Elf32_Word EF_ARM_EABI_VER5 = 0x05000000; +const Elf32_Word EF_ARM_VFP_FLOAT = 0x400; +const Elf32_Word EF_ARM_SOFT_FLOAT = 0x200; + +/* + * Elf program header + */ +struct Elf32_Phdr +{ + Elf32_Word p_type; // Program header type, any of the PH_* constants + Elf32_Off p_offset; // Segment start offset in file + Elf32_Addr p_vaddr; // Segment virtual address + Elf32_Addr p_paddr; // Segment physical address + Elf32_Word p_filesz; // Segment size in file + Elf32_Word p_memsz; // Segment size in memory + Elf32_Word p_flags; // Segment flasgs, any of the PF_* constants + Elf32_Word p_align; // Segment alignment requirements +} __attribute__((packed)); + +// Values for p_type +const Elf32_Word PT_NULL = 0; // Unused array entry +const Elf32_Word PT_LOAD = 1; // Loadable segment +const Elf32_Word PT_DYNAMIC = 2; // Segment is the dynamic section +const Elf32_Word PT_INTERP = 3; // Shared library interpreter +const Elf32_Word PT_NOTE = 4; // Auxiliary information + +// Values for p_flags +const Elf32_Word PF_X = 0x1; // Execute +const Elf32_Word PF_W = 0x2; // Write +const Elf32_Word PF_R = 0x4; // Read + +/* + * Entries of the DYNAMIC segment + */ +struct Elf32_Dyn +{ + Elf32_Sword d_tag; // Type of entry + union { + Elf32_Word d_val; // Value of entry, if number + Elf32_Addr d_ptr; // Value of entry, if offset into the file + } d_un; +} __attribute__((packed)); + +// Values for d_tag +const int DT_NULL = 0; +const int DT_NEEDED = 1; +const int DT_PLTRELSZ = 2; +const int DT_PLTGOT = 3; +const int DT_HASH = 4; +const int DT_STRTAB = 5; +const int DT_SYMTAB = 6; +const int DT_RELA = 7; +const int DT_RELASZ = 8; +const int DT_RELAENT = 9; +const int DT_STRSZ = 10; +const int DT_SYMENT = 11; +const int DT_INIT = 12; +const int DT_FINI = 13; +const int DT_SONAME = 14; +const int DT_RPATH = 15; +const int DT_SYMBOLIC = 16; +const int DT_REL = 17; +const int DT_RELSZ = 18; +const int DT_RELENT = 19; +const int DT_PLTREL = 20; +const int DT_DEBUG = 21; +const int DT_TEXTREL = 22; +const int DT_JMPREL = 23; +const int DT_BINDNOW = 24; +const int DT_MX_RAMSIZE = 0x10000000; //Miosix specific, RAM size +const int DT_MX_STACKSIZE = 0x10000001; //Miosix specific, STACK size +const int DT_MX_ABI = 0x736f694d; //Miosix specific, ABI version +const unsigned int DV_MX_ABI_V0 = 0x00007869; //Miosix specific, ABI version 0 +const unsigned int DV_MX_ABI_V1 = 0x01007869; //Miosix specific, ABI version 1 + +/* + * Relocation entries + */ +struct Elf32_Rel +{ + Elf32_Addr r_offset; + Elf32_Word r_info; +} __attribute__((packed)); + +// To extract the two fields of r_info +#define ELF32_R_SYM(i) ((i)>>8) +#define ELF32_R_TYPE(i) ((unsigned char)(i)) + + +// Possible values for ELF32_R_TYPE(r_info) +const unsigned char R_ARM_NONE = 0; +const unsigned char R_ARM_ABS32 = 2; +const unsigned char R_ARM_RELATIVE = 23; + +} //namespace miosix + +#endif //WITH_PROCESSES + +#endif //ELF_TYPES_H diff --git a/lib/miosix-kernel/miosix/kernel/error.cpp b/lib/miosix-kernel/miosix/kernel/error.cpp new file mode 100644 index 00000000..173c3229 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/error.cpp @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "error.h" +#include "kernel.h" +#include "interfaces/portability.h" +#include "interfaces/bsp.h" +#include "logging.h" + +namespace miosix { + +void errorHandler(Error e) +{ + // Here we must be careful since this function can be called within an + // interrupt routine, and disabling interrupts within an interrupt + // routine must be avoided. + bool interrupts=areInterruptsEnabled(); + if(interrupts) disableInterrupts(); + + //Recoverable errors: None + + //Unrecoverable errors + switch(e) + { + + case OUT_OF_MEMORY: + IRQerrorLog("\r\n***Out of memory\r\n"); + break; + case STACK_OVERFLOW: + IRQerrorLog("\r\n***Stack overflow\r\n"); + break; + case UNEXPECTED: + IRQerrorLog("\r\n***Unexpected error\r\n"); + break; + case PAUSE_KERNEL_NESTING: + IRQerrorLog("\r\n***Pause kernel nesting\r\n"); + break; + case DISABLE_INTERRUPTS_NESTING: + IRQerrorLog("\r\n***Disable interrupt nesting\r\n"); + break; + case MUTEX_DEADLOCK: + IRQerrorLog("\r\n***Deadlock\r\n"); + break; + case NESTING_OVERFLOW: + IRQerrorLog("\r\n***Nesting overflow\r\n"); + break; + case INTERRUPTS_ENABLED_AT_BOOT: + IRQerrorLog("\r\n***Interrupts enabled at boot\r\n"); + break; + default: + break; + } + miosix_private::IRQsystemReboot(); + + //if(interrupts) enableInterrupts(); // Not needed since no recoverable errors +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/kernel/error.h b/lib/miosix-kernel/miosix/kernel/error.h new file mode 100644 index 00000000..2273e78c --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/error.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef ERROR_H +#define ERROR_H + +namespace miosix { + +/** + * \enum Error + * This enum will be passed as argument to the error handler.
+ * If the error is marked UNRECOVERABLE, then the error handler will not return. + */ +enum Error +{ + /// The heap is full, malloc/new returned NULL.
Error is UNRECOVERABLE + OUT_OF_MEMORY, + + /// The stack of a thread overflowed.
Error is UNRECOVERABLE + STACK_OVERFLOW, + + /// Unexpected error occurred.
Error is UNRECOVERABLE + UNEXPECTED, + + /// A call to restartKernel that does not match a previous call + /// to pauseKernel
Error is UNRECOVERABLE + PAUSE_KERNEL_NESTING, + + /// A call to enableInterrupts that does not match a previous call + /// to disableInterrupts
Error is UNRECOVERABLE + DISABLE_INTERRUPTS_NESTING, + + /// An attempt to lock twice a non recursive mutex happened.
+ /// Error is UNRECOVERABLE + MUTEX_DEADLOCK, + + /// The calls to pauseKernel or disableInterrupts were nested too + ///much. Error is UNRECOVERABLE + NESTING_OVERFLOW, + + /// Interrupts are wrongly enabled during boot + /// Error is UNRECOVERABLE + INTERRUPTS_ENABLED_AT_BOOT +}; + +/** + * Handles errors generated by kernel. + * Prints an error message on the Console (only if WITH_ERRLOG is defined + * in miosix_config.h). + * For information about possible errors, see the enum Error + * Can be called with the kernel not started, started, paused, with interrupts + * disabled and within an interrupt routine. + */ +void errorHandler(Error e); + +} //namespace miosix + +#endif //ERROR_H diff --git a/lib/miosix-kernel/miosix/kernel/intrusive.h b/lib/miosix-kernel/miosix/kernel/intrusive.h new file mode 100644 index 00000000..54442f5d --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/intrusive.h @@ -0,0 +1,639 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef INTRUSIVE_H +#define INTRUSIVE_H + +#include +#include +#include +#include "interfaces/atomic_ops.h" + +#if __cplusplus > 199711L +#include +#endif // c++11 + +namespace miosix { + +// Forward decls +template +class intrusive_ref_ptr; + +/** + * Base class from which all intrusive objects derive, contains the + * data that intrusive objects must have + */ +class Intrusive +{ +protected: + union + { + int referenceCount; ///< Used in IntrusiveRefCounted + //Intrusive *next; ///< Used by the cleanup list, if it will be done + } intrusive; + //virtual ~Intrusive() {} ///< To allow deleting from the cleanup list +}; + +/** + * Derive from this class to support intrusive reference counting + */ +class IntrusiveRefCounted : public Intrusive +{ +protected: + /** + * Constructor, initializes the reference count + */ + IntrusiveRefCounted() { intrusive.referenceCount=0; } + + /** + * Copy constructor + */ + IntrusiveRefCounted(const IntrusiveRefCounted&) + { + // The default copy constructor would have copied the reference count, + // but that's wrong, a new object is being built, and its reference + // count should be zero + intrusive.referenceCount=0; + } + + /** + * Overload of operator= + */ + IntrusiveRefCounted& operator=(const IntrusiveRefCounted&) + { + // The default copy constructor would have copied the reference count, + // but that's wrong, as we have two distinct object (i.e, chunks of + // memory to be managed), and their reference counts need to stay + // separate, so do nothing in operator= + return *this; + } + + // No destructor + +private: + template + friend class intrusive_ref_ptr; ///< To access the reference count +}; + +/** + * An implementation of 20.7.2.4 enable_shared_from_this for IntrusiveRefCounted + * \param T this class uses the CRTP, just like enable_shared_from_this, so if + * class Foo derives from IntrusiveRefCountedSharedFromThis, T has to be Foo. + */ +template +class IntrusiveRefCountedSharedFromThis +{ +public: + /** + * Constructor + */ + IntrusiveRefCountedSharedFromThis() + { + static_assert(std::is_base_of::value,""); + } + + /** + * \return an intrusive_ref_ptr to this. In this respect, + * IntrusiveRefCounted also behaves like enable_shared_from_this + */ + intrusive_ref_ptr shared_from_this() + { + // Simply making an intrusive_ref_ptr from this works as the reference + // count is intrusive + #ifndef __NO_EXCEPTIONS + T* result=dynamic_cast(this); + assert(result); + #else //__NO_EXCEPTIONS + T* result=static_cast(this); + #endif //__NO_EXCEPTIONS + return intrusive_ref_ptr(result); + } + + /** + * \return an intrusive_ref_ptr to this In this respect, + * IntrusiveRefCounted also behaves like enable_shared_from_this + */ + intrusive_ref_ptr shared_from_this() const + { + // Simply making an intrusive_ref_ptr from this works as the reference + // count is intrusive + #ifndef __NO_EXCEPTIONS + const T* result=dynamic_cast(this); + assert(result); + #else //__NO_EXCEPTIONS + const T* result=static_cast(this); + #endif //__NO_EXCEPTIONS + return intrusive_ref_ptr(result); + } + + /** + * Destructor + */ + virtual ~IntrusiveRefCountedSharedFromThis(); +}; + +template +IntrusiveRefCountedSharedFromThis::~IntrusiveRefCountedSharedFromThis() {} + +/** + * Reference counted pointer to intrusively reference counted objects. + * This class is made in a way to resemble the shared_ptr class, as specified + * in chapter 20.7.2.2 of the C++11 standard. + * + * Thread safety of this class is the same as shared_ptr. The reference + * count is updated using atomic operations, but this does not make + * all kind of concurrent access to the same intrusive_ref_ptr safe. + * A good reference that explains why is + * www.drdobbs.com/cpp/a-base-class-for-intrusively-reference-c/229218807?pgno=3 + * + * \param T type to which the object points + */ +template +class intrusive_ref_ptr +{ +public: + typedef T element_type; ///< As shared_ptr, expose the managed type + + /** + * Default constructor + */ + intrusive_ref_ptr() : object(0) {} + + /** + * Constructor, with raw pointer + * \param object object to manage + */ + explicit intrusive_ref_ptr(T *o) : object(o) + { + incrementRefCount(); + } + + /** + * Generalized constructor, with raw pointer + * \param object object to manage + */ + template + explicit intrusive_ref_ptr(U *o) : object(o) + { + incrementRefCount(); + + //Disallow polimorphic upcasting of non polimorphic classes, + //as this will lead to bugs + static_assert(std::has_virtual_destructor::value,""); + } + + /** + * Copy constructor, with same type of managed pointer + * \param rhs object to manage + */ + intrusive_ref_ptr(const intrusive_ref_ptr& rhs) : object(rhs.object) + { + incrementRefCount(); + } + + /** + * Generalized copy constructor, to support upcast among refcounted + * pointers + * \param rhs object to manage + */ + template + intrusive_ref_ptr(const intrusive_ref_ptr& rhs) : object(rhs.get()) + { + incrementRefCount(); + + //Disallow polimorphic upcasting of non polimorphic classes, + //as this will lead to bugs + static_assert(std::has_virtual_destructor::value,""); + } + + /** + * Operator=, with same type of managed pointer + * \param rhs object to manage + * \return a reference to *this + */ + intrusive_ref_ptr& operator= (const intrusive_ref_ptr& rhs); + + /** + * Generalized perator=, to support upcast among refcounted pointers + * \param rhs object to manage + * \return a reference to *this + */ + template + intrusive_ref_ptr& operator= (const intrusive_ref_ptr& rhs); + + /** + * Operator=, with raw pointer + * \param rhs object to manage + * \return a reference to *this + */ + intrusive_ref_ptr& operator= (T* o); + + /** + * \return a pointer to the managed object + */ + T *get() const { return object; } + + /** + * \return a reference to the managed object + */ + T& operator*() const { return *object; } + + /** + * Allows this class to behave like the managed type + * \return a pointer to the managed object + */ + T *operator->() const { return object; } + + //Safe bool idiom + struct SafeBoolStruct { void* b; }; + typedef void* SafeBoolStruct::* SafeBool; + + /** + * \return true if the object contains a callback + */ + operator SafeBool() const + { + return object==0 ? 0 : &SafeBoolStruct::b; + } + + /** + * Swap the managed object with another intrusive_ref_ptr + * \param rhs the other smart pointer + */ + void swap(intrusive_ref_ptr& rhs) { std::swap(object,rhs.object); } + + /** + * After a call to this member function, this intrusive_ref_ptr + * no longer points to the managed object. The managed object is + * deleted if this was the only pointer at it + */ + void reset() + { + if(decrementRefCount()) delete object; + // Object needs to be set to 0 regardless + // of whether the object is deleted + object=0; + } + + /** + * \return the number of intrusive_ref_ptr that point to the managed object. + * If return 0, than this points to nullptr + */ + int use_count() const + { + if(!object) return 0; + return object->intrusive.referenceCount; + } + + /** + * \internal + * This is just an implementation detail. + * Use the free function atomic_load instead. + * \return a copy of *this + */ + intrusive_ref_ptr atomic_load() const; + + /** + * \internal + * This is just an implementation detail. + * Use the free function atomic_store instead. + * Note that this member function is not equivalent to the free function + * with the same name, as it takes its argument by reference. This may + * cause problems in some multithreaded use cases, so don't use it directly + * \param r object to store to *this + * \return the previous value stored in *this + */ + intrusive_ref_ptr atomic_exchange(intrusive_ref_ptr& r); + + /** + * Destructor + */ + ~intrusive_ref_ptr() { reset(); } +private: + + /** + * Increments the reference count + */ + void incrementRefCount() + { + if(object) atomicAdd(&object->intrusive.referenceCount,1); + } + + /** + * Decrements the reference count + * \return true if the object has to be deleted + */ + bool decrementRefCount() + { + if(object==0) return false; + return atomicAddExchange(&object->intrusive.referenceCount,-1)==1; + } + + T *object; ///< The managed object +}; + +template +intrusive_ref_ptr& intrusive_ref_ptr::operator= + (const intrusive_ref_ptr& rhs) +{ + if(*this==rhs) return *this; //Handle assignment to self + if(decrementRefCount()) delete object; + object=rhs.object; + incrementRefCount(); + return *this; +} + +template template +intrusive_ref_ptr& intrusive_ref_ptr::operator= + (const intrusive_ref_ptr& rhs) +{ + if(*this==rhs) return *this; //Handle assignment to self + if(decrementRefCount()) delete object; + object=rhs.get(); + incrementRefCount(); + + //Disallow polimorphic upcasting of non polimorphic classes, + //as this will lead to bugs + static_assert(std::has_virtual_destructor::value,""); + + return *this; +} + +template +intrusive_ref_ptr& intrusive_ref_ptr::operator= (T* o) +{ + if(decrementRefCount()) delete object; + object=o; + incrementRefCount(); + return *this; +} + +template +intrusive_ref_ptr intrusive_ref_ptr::atomic_load() const +{ + intrusive_ref_ptr result; // This gets initialized with 0 + + // According to the C++ standard, this causes undefined behaviour if + // T has virtual functions, but GCC (and clang) have an implementation + // based on a compiler intrinsic that does the right thing: it produces + // the correct answer together with a compiler warning for classes with + // virtual destructors, and even for classes using multiple inheritance. + // It (obviously) fails for classes making use of virtual inheritance, but + // in this case the warning is promoted to an error. The last thing to + // fix is to remove the warning using the #pragma + // As a result, intrusive_ref_ptr can't be used with classes that have + // virtual base classes, but that's an acceptable limitation, especially + // considering that you get a meaningful compiler error if accidentally + // trying to use it in such a case. + #pragma GCC diagnostic ignored "-Winvalid-offsetof" + const int offsetBytes=offsetof(T,intrusive.referenceCount); + #pragma GCC diagnostic pop + + // Check that referenceCount is properly aligned for the following code to work + static_assert((offsetBytes % sizeof(int))==0, ""); + + const int offsetInt=offsetBytes/sizeof(int); + void * const volatile * objectPtrAddr= + reinterpret_cast(&object); + void *loadedObject=atomicFetchAndIncrement(objectPtrAddr,offsetInt,1); + // This does not increment referenceCount, as it was done before + result.object=reinterpret_cast(loadedObject); + return result; +} + +template +intrusive_ref_ptr intrusive_ref_ptr::atomic_exchange( + intrusive_ref_ptr& r) +{ + //Note: this is safe with respect to assignment to self + T *temp=r.object; + if(temp) atomicAdd(&temp->intrusive.referenceCount,1); + + // Check that the following reinterpret_casts will work as intended. + // This also means that this code won't work on 64bit machines but for + // Miosix this isn't a problem for now. + static_assert(sizeof(void*)==sizeof(int),""); + + int tempInt=reinterpret_cast(temp); + volatile int *objectAddrInt=reinterpret_cast(&object); + temp=reinterpret_cast(atomicSwap(objectAddrInt,tempInt)); + + intrusive_ref_ptr result; // This gets initialized with 0 + // This does not increment referenceCount, as the pointer was swapped + result.object=temp; + return result; +} + +/** + * Operator== + * \param a first pointer + * \param b second pointer + * \return true if they point to the same object + */ +template +bool operator==(const intrusive_ref_ptr& a, const intrusive_ref_ptr& b) +{ + return a.get()==b.get(); +} + +template +bool operator==(const T *a, const intrusive_ref_ptr& b) +{ + return a==b.get(); +} + +template +bool operator==(const intrusive_ref_ptr& a, const T *b) +{ + return a.get()==b; +} + +/** + * Operator!= + * \param a first pointer + * \param b second pointer + * \return true if they point to different objects + */ +template +bool operator!=(const intrusive_ref_ptr& a, const intrusive_ref_ptr& b) +{ + return a.get()!=b.get(); +} + +template +bool operator!=(const T *a, const intrusive_ref_ptr& b) +{ + return a!=b.get(); +} + +template +bool operator!=(const intrusive_ref_ptr& a, const T *b) +{ + return a.get()!=b; +} + +/** + * Operator<, allows to create containers of objects + * \param a first pointer + * \param b second pointer + * \return true if a.get() < b.get() + */ +template +bool operator<(const intrusive_ref_ptr& a, const intrusive_ref_ptr& b) +{ + return a.get() +bool operator<(const T *a, const intrusive_ref_ptr& b) +{ + return a +bool operator<(const intrusive_ref_ptr& a, const T *b) +{ + return a.get() +std::ostream& operator<<(std::ostream& os, const intrusive_ref_ptr& p) +{ + os< +intrusive_ref_ptr static_pointer_cast(const intrusive_ref_ptr& r) +{ + // Note: 20.7.2.2.9 says this expression done with shared_ptr causes + // undefined behaviour, however this works with intrusive_ref_ptr + // as the reference count is intrusive, so the counter isn't duplicated. + // To ease porting of code to and from shared_ptr it is recomended + // to use static_pointer_cast, anyway. + return intrusive_ref_ptr(static_cast(r.get())); +} + +/** + * Performs dynamic_cast between intrusive_ref_ptr + * \param r intrusive_ref_ptr of source type + * \return intrusive_ref_ptr of destination type + */ +template +intrusive_ref_ptr dynamic_pointer_cast(const intrusive_ref_ptr& r) +{ + // Note: 20.7.2.2.9 says this expression done with shared_ptr causes + // undefined behaviour, however this works with intrusive_ref_ptr + // as the reference count is intrusive, so the counter isn't duplicated. + // To ease porting of code to and from shared_ptr it is recomended + // to use static_pointer_cast, anyway. + return intrusive_ref_ptr(dynamic_cast(r.get())); +} + +/** + * Performs const_cast between intrusive_ref_ptr + * \param r intrusive_ref_ptr of source type + * \return intrusive_ref_ptr of destination type + */ +template +intrusive_ref_ptr const_pointer_cast(const intrusive_ref_ptr& r) +{ + // Note: 20.7.2.2.9 says this expression done with shared_ptr causes + // undefined behaviour, however this works with intrusive_ref_ptr + // as the reference count is intrusive, so the counter isn't duplicated. + // To ease porting of code to and from shared_ptr it is recomended + // to use static_pointer_cast, anyway. + return intrusive_ref_ptr(const_cast(r.get())); +} + +/** + * Allows concurrent access to an instance of intrusive_ref_ptr. + * Multiple threads can cooncurrently perform atomic_load(), atomic_store() + * and atomic_exchange() on the same intrusive_ref_ptr. Any other concurent + * access not protected by explicit locking (such as threads calling reset(), + * or using the copy constructor, or deleting the intrusive_ref_ptr) yields + * undefined behaviour. + * \param p pointer to an intrusive_ref_ptr shared among threads + * \return *p, atomically fetching the pointer and incrementing the reference + * count + */ +template +intrusive_ref_ptr atomic_load(const intrusive_ref_ptr *p) +{ + if(p==0) return intrusive_ref_ptr(); + return p->atomic_load(); +} + +/** + * Allows concurrent access to an instance of intrusive_ref_ptr. + * Multiple threads can cooncurrently perform atomic_load(), atomic_store() + * and atomic_exchange() on the same intrusive_ref_ptr. Any other concurent + * access not protected by explicit locking (such as threads calling reset(), + * or using the copy constructor, or deleting the intrusive_ref_ptr) yields + * undefined behaviour. + * \param p pointer to an intrusive_ref_ptr shared among threads + * \param r intrusive_ref_ptr that will be stored in *p + */ +template +void atomic_store(intrusive_ref_ptr *p, intrusive_ref_ptr r) +{ + if(p) p->atomic_exchange(r); +} + +/** + * Allows concurrent access to an instance of intrusive_ref_ptr. + * Multiple threads can cooncurrently perform atomic_load(), atomic_store() + * and atomic_exchange() on the same intrusive_ref_ptr. Any other concurent + * access not protected by explicit locking (such as threads calling reset(), + * or using the copy constructor, or deleting the intrusive_ref_ptr) yields + * undefined behaviour. + * \param p pointer to an intrusive_ref_ptr shared among threads + * \param r value to be stored in *p + * \return the previous value of *p + */ +template +intrusive_ref_ptr atomic_exchange(intrusive_ref_ptr *p, + intrusive_ref_ptr r) +{ + if(p==0) return intrusive_ref_ptr(); + return p->atomic_exchange(r); +} + +} //namenpace miosix + +#endif //INTRUSIVE_H diff --git a/lib/miosix-kernel/miosix/kernel/kernel.cpp b/lib/miosix-kernel/miosix/kernel/kernel.cpp new file mode 100644 index 00000000..b0b8aa7e --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/kernel.cpp @@ -0,0 +1,842 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#include "kernel.h" +#include "interfaces/portability.h" +#include "interfaces/atomic_ops.h" +#include "error.h" +#include "logging.h" +#include "arch_settings.h" +#include "sync.h" +#include "stage_2_boot.h" +#include "process.h" +#include "kernel/scheduler/scheduler.h" +#include "stdlib_integration/libc_integration.h" +#include +#include +#include +#include + +/* +Used by assembler context switch macros +This variable is set by miosix::IRQfindNextThread in file kernel.cpp +*/ +extern "C" { +volatile unsigned int *ctxsave; +} + + +namespace miosix { + +//Global variables used by kernel.cpp. Those that are not static are also used +//in portability.cpp and by the schedulers. +//These variables MUST NOT be used outside kernel.cpp and portability.cpp + +volatile Thread *cur=NULL;///<\internal Thread currently running + +///\internal True if there are threads in the DELETED status. Used by idle thread +static volatile bool exist_deleted=false; + +static SleepData *sleeping_list=NULL;///<\internal list of sleeping threads + +static volatile long long tick=0;///<\internal Kernel tick + +///\internal !=0 after pauseKernel(), ==0 after restartKernel() +volatile int kernel_running=0; + +///\internal true if a tick occurs while the kernel is paused +volatile bool tick_skew=false; + +static bool kernel_started=false;///<\internal becomes true after startKernel. + +/// This is used by disableInterrupts() and enableInterrupts() to allow nested +/// calls to these functions. +static unsigned char interruptDisableNesting=0; + +#ifdef WITH_PROCESSES + +/// The proc field of the Thread class for kernel threads points to this object +static ProcessBase *kernel=nullptr; + +#endif //WITH_PROCESSES + +/** + * \internal + * Idle thread. Created when the kernel is started, it phisically deallocates + * memory for deleted threads, and puts the cpu in sleep mode. + */ +void *idleThread(void *argv) +{ + (void) argv; + + for(;;) + { + if(exist_deleted) + { + PauseKernelLock lock; + exist_deleted=false; + Scheduler::PKremoveDeadThreads(); + } + #ifndef JTAG_DISABLE_SLEEP + //JTAG debuggers lose communication with the device if it enters sleep + //mode, so to use debugging it is necessary to remove this instruction + miosix_private::sleepCpu(); + #endif + } + return 0; //Just to avoid a compiler warning +} + +void disableInterrupts() +{ + //Before the kernel is started interrupts are disabled, + //so disabling them again won't hurt + miosix_private::doDisableInterrupts(); + if(interruptDisableNesting==0xff) errorHandler(NESTING_OVERFLOW); + interruptDisableNesting++; +} + +void enableInterrupts() +{ + if(interruptDisableNesting==0) + { + //Bad, enableInterrupts was called one time more than disableInterrupts + errorHandler(DISABLE_INTERRUPTS_NESTING); + } + interruptDisableNesting--; + if(interruptDisableNesting==0 && kernel_started==true) + { + miosix_private::doEnableInterrupts(); + } +} + +void pauseKernel() +{ + int old=atomicAddExchange(&kernel_running,1); + if(old>=0xff) errorHandler(NESTING_OVERFLOW); +} + +void restartKernel() +{ + int old=atomicAddExchange(&kernel_running,-1); + if(old<=0) errorHandler(PAUSE_KERNEL_NESTING); + + //Check interruptDisableNesting to allow pauseKernel() while interrupts + //are disabled with an InterruptDisableLock + if(interruptDisableNesting==0) + { + //If we missed some tick yield immediately + if(old==1 && tick_skew) + { + tick_skew=false; + Thread::yield(); + } + } +} + +bool areInterruptsEnabled() +{ + return miosix_private::checkAreInterruptsEnabled(); +} + +void startKernel() +{ + #ifdef WITH_PROCESSES + try { + kernel=new ProcessBase; + } catch(...) { + errorHandler(OUT_OF_MEMORY); + } + #endif //WITH_PROCESSES + + // As a side effect this function allocates the idle thread and makes cur + // point to it. It's probably been called many times during boot by the time + // we get here, but we can't be sure + auto *idle=Thread::IRQgetCurrentThread(); + + #ifdef WITH_PROCESSES + // If the idle thread was allocated before startKernel(), then its proc + // is nullptr. We can't move kernel=new ProcessBase; earlier than this + // function, though + idle->proc=kernel; + #endif //WITH_PROCESSES + + // Create the idle and main thread + Thread *main; + main=Thread::doCreate(mainLoader,MAIN_STACK_SIZE,nullptr,Thread::DEFAULT,true); + if(main==nullptr) errorHandler(OUT_OF_MEMORY); + + // Add them to the scheduler + if(Scheduler::PKaddThread(main,MAIN_PRIORITY)==false) errorHandler(UNEXPECTED); + + // Idle thread needs to be set after main (see control_scheduler.cpp) + Scheduler::IRQsetIdleThread(idle); + + // Make the C standard library use per-thread reeentrancy structure + setCReentrancyCallback(Thread::getCReent); + + // Now kernel is started + kernel_started=true; + + // Dispatch the task to the architecture-specific function + miosix_private::IRQportableStartKernel(); +} + +bool isKernelRunning() +{ + return (kernel_running==0) && kernel_started; +} + +long long getTick() +{ + /* + * Reading a volatile 64bit integer on a 32bit platform with interrupts + * enabled is tricky because the operation is not atomic, so we need to + * read it twice to see if we were interrupted in the middle of the read + * operation. + */ + long long a,b; + for(;;) + { + a=static_cast(tick); + b=static_cast(tick); + if(a==b) return a; + } +} + +/** + * \internal + * Used by Thread::sleep() to add a thread to sleeping list. The list is sorted + * by the wakeup_time field to reduce time required to wake threads during + * context switch. + * Also sets thread SLEEP_FLAG. It is labeled IRQ not because it is meant to be + * used inside an IRQ, but because interrupts must be disabled prior to calling + * this function. + */ +void IRQaddToSleepingList(SleepData *x) +{ + x->p->flags.IRQsetSleep(true); + if((sleeping_list==NULL)||(x->wakeup_time <= sleeping_list->wakeup_time)) + { + x->next=sleeping_list; + sleeping_list=x; + } else { + SleepData *cur=sleeping_list; + for(;;) + { + if((cur->next==NULL)||(x->wakeup_time <= cur->next->wakeup_time)) + { + x->next=cur->next; + cur->next=x; + break; + } + cur=cur->next; + } + } +} + +/** + * \internal + * Called @ every tick to check if it's time to wake some thread. + * Also increases the system tick. + * Takes care of clearing SLEEP_FLAG. + * It is used by the kernel, and should not be used by end users. + * \return true if some thread was woken. + */ +bool IRQwakeThreads() +{ + tick++;//Increment tick + bool result=false; + for(;;) + { + if(sleeping_list==NULL) break;//If no item in list, return + //Since list is sorted, if we don't need to wake the first element + //we don't need to wake the other too + if(tick != sleeping_list->wakeup_time) break; + sleeping_list->p->flags.IRQsetSleep(false);//Wake thread + sleeping_list=sleeping_list->next;//Remove from list + result=true; + } + return result; +} + +/* +Memory layout for a thread + |------------------------| + | class Thread | + |------------------------|<-- this + | stack | + | | | + | V | + |------------------------| + | watermark | + |------------------------|<-- base, watermark +*/ + +Thread *Thread::create(void *(*startfunc)(void *), unsigned int stacksize, + Priority priority, void *argv, unsigned short options) +{ + //Check to see if input parameters are valid + if(priority.validate()==false || stacksizewatermark; + thread->~Thread(); + free(base); //Delete ALL thread memory + return NULL; + } + } + #ifdef SCHED_TYPE_EDF + if(isKernelRunning()) yield(); //The new thread might have a closer deadline + #endif //SCHED_TYPE_EDF + return thread; +} + +Thread *Thread::create(void (*startfunc)(void *), unsigned int stacksize, + Priority priority, void *argv, unsigned short options) +{ + //Just call the other version with a cast. + return Thread::create(reinterpret_cast(startfunc), + stacksize,priority,argv,options); +} + +void Thread::yield() +{ + miosix_private::doYield(); +} + +bool Thread::testTerminate() +{ + //Just reading, no need for critical section + return const_cast(cur)->flags.isDeleting(); +} + +void Thread::sleep(unsigned int ms) +{ + if(ms==0) return; + //The SleepData variable has to be in scope till Thread::yield() returns + //as IRQaddToSleepingList() makes it part of a linked list till the + //thread wakes up (i.e: after Thread::yield() returns) + SleepData d; + //pauseKernel() here is not enough since even if the kernel is stopped + //the tick isr will wake threads, modifying the sleeping_list + { + FastInterruptDisableLock lock; + d.p=const_cast(cur); + if(((ms*TICK_FREQ)/1000)>0) d.wakeup_time=getTick()+(ms*TICK_FREQ)/1000; + //If tick resolution is too low, wait one tick + else d.wakeup_time=getTick()+1; + IRQaddToSleepingList(&d);//Also sets SLEEP_FLAG + } + Thread::yield(); +} + +void Thread::sleepUntil(long long absoluteTime) +{ + //The SleepData variable has to be in scope till Thread::yield() returns + //as IRQaddToSleepingList() makes it part of a linked list till the + //thread wakes up (i.e: after Thread::yield() returns) + SleepData d; + //pauseKernel() here is not enough since even if the kernel is stopped + //the tick isr will wake threads, modifying the sleeping_list + { + FastInterruptDisableLock lock; + if(absoluteTime<=getTick()) return; //Wakeup time in the past, return + d.p=const_cast(cur); + d.wakeup_time=absoluteTime; + IRQaddToSleepingList(&d);//Also sets SLEEP_FLAG + } + Thread::yield(); +} + +Thread *Thread::getCurrentThread() +{ + Thread *result=const_cast(cur); + if(result) return result; + //This function must always return a pointer to a valid thread. The first + //time this is called before the kernel is started, however, cur is nullptr. + //thus we allocate the idle thread and return a pointer to that. + return allocateIdleThread(); +} + +bool Thread::exists(Thread *p) +{ + if(p==NULL) return false; + PauseKernelLock lock; + return Scheduler::PKexists(p); +} + +Priority Thread::getPriority() +{ + return Scheduler::getPriority(this); +} + +void Thread::setPriority(Priority pr) +{ + if(pr.validate()==false) return; + PauseKernelLock lock; + + Thread *current=getCurrentThread(); + //If thread is locking at least one mutex + if(current->mutexLocked!=0) + { + //savedPriority always changes, since when all mutexes are unlocked + //setPriority() must become effective + if(current->savedPriority==pr) return; + current->savedPriority=pr; + //Calculate new priority of thread, which is + //max(savedPriority, inheritedPriority) + Mutex *walk=current->mutexLocked; + while(walk!=0) + { + if(walk->waiting.empty()==false) + pr=std::max(pr,walk->waiting.front()->getPriority()); + walk=walk->next; + } + } + + //If old priority == desired priority, nothing to do. + if(pr==current->getPriority()) return; + Scheduler::PKsetPriority(current,pr); + #ifdef SCHED_TYPE_EDF + if(isKernelRunning()) yield(); //Another thread might have a closer deadline + #endif //SCHED_TYPE_EDF +} + +void Thread::terminate() +{ + //doing a read-modify-write operation on this->status, so pauseKernel is + //not enough, we need to disable interrupts + FastInterruptDisableLock lock; + this->flags.IRQsetDeleting(); +} + +void Thread::wait() +{ + //pausing the kernel is not enough because of IRQwait and IRQwakeup + { + FastInterruptDisableLock lock; + const_cast(cur)->flags.IRQsetWait(true); + } + Thread::yield(); + //Return here after wakeup +} + +void Thread::wakeup() +{ + //pausing the kernel is not enough because of IRQwait and IRQwakeup + { + FastInterruptDisableLock lock; + this->flags.IRQsetWait(false); + } + #ifdef SCHED_TYPE_EDF + yield();//The other thread might have a closer deadline + #endif //SCHED_TYPE_EDF +} + +void Thread::PKwakeup() +{ + //pausing the kernel is not enough because of IRQwait and IRQwakeup + FastInterruptDisableLock lock; + this->flags.IRQsetWait(false); +} + +void Thread::detach() +{ + FastInterruptDisableLock lock; + this->flags.IRQsetDetached(); + + //we detached a terminated thread, so its memory needs to be deallocated + if(this->flags.isDeletedJoin()) exist_deleted=true; + + //Corner case: detaching a thread, but somebody else already called join + //on it. This makes join return false instead of deadlocking + if(this->joinData.waitingForJoin!=NULL) + { + //joinData is an union, so its content can be an invalid thread + //this happens if detaching a thread that has already terminated + if(this->flags.isDeletedJoin()==false) + { + //Wake thread, or it might sleep forever + this->joinData.waitingForJoin->flags.IRQsetJoinWait(false); + } + } +} + +bool Thread::isDetached() const +{ + return this->flags.isDetached(); +} + +bool Thread::join(void** result) +{ + { + FastInterruptDisableLock dLock; + if(this==Thread::IRQgetCurrentThread()) return false; + if(Thread::IRQexists(this)==false) return false; + if(this->flags.isDetached()) return false; + if(this->flags.isDeletedJoin()==false) + { + //Another thread already called join on toJoin + if(this->joinData.waitingForJoin!=NULL) return false; + + this->joinData.waitingForJoin=Thread::IRQgetCurrentThread(); + for(;;) + { + //Wait + Thread::IRQgetCurrentThread()->flags.IRQsetJoinWait(true); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + if(Thread::IRQexists(this)==false) return false; + if(this->flags.isDetached()) return false; + if(this->flags.isDeletedJoin()) break; + } + } + //Thread deleted, complete join procedure + //Setting detached flag will make isDeleted() return true, + //so its memory can be deallocated + this->flags.IRQsetDetached(); + if(result!=NULL) *result=this->joinData.result; + } + { + PauseKernelLock lock; + //Since there is surely one dead thread, deallocate it immediately + //to free its memory as soon as possible + Scheduler::PKremoveDeadThreads(); + } + return true; +} + +Thread *Thread::IRQgetCurrentThread() +{ + //Implementation is the same as getCurrentThread, but to keep a consistent + //interface this method is duplicated + Thread *result=const_cast(cur); + if(result) return result; + //This function must always return a pointer to a valid thread. The first + //time this is called before the kernel is started, however, cur is nullptr. + //thus we allocate the idle thread and return a pointer to that. + return allocateIdleThread(); +} + +Priority Thread::IRQgetPriority() +{ + //Implementation is the same as getPriority, but to keep a consistent + //interface this method is duplicated + return Scheduler::IRQgetPriority(this); +} + +void Thread::IRQwait() +{ + const_cast(cur)->flags.IRQsetWait(true); +} + +void Thread::IRQwakeup() +{ + this->flags.IRQsetWait(false); +} + +bool Thread::IRQexists(Thread* p) +{ + if(p==NULL) return false; + return Scheduler::PKexists(p); +} + +const unsigned int *Thread::getStackBottom() +{ + return getCurrentThread()->watermark+(WATERMARK_LEN/sizeof(unsigned int)); +} + +int Thread::getStackSize() +{ + return getCurrentThread()->stacksize; +} + +Thread *Thread::doCreate(void*(*startfunc)(void*) , unsigned int stacksize, + void* argv, unsigned short options, bool defaultReent) +{ + unsigned int fullStackSize=WATERMARK_LEN+CTXSAVE_ON_STACK+stacksize; + + //Align fullStackSize to the platform required stack alignment + fullStackSize+=CTXSAVE_STACK_ALIGNMENT-1; + fullStackSize/=CTXSAVE_STACK_ALIGNMENT; + fullStackSize*=CTXSAVE_STACK_ALIGNMENT; + + //Allocate memory for the thread, return if fail + unsigned int *base=static_cast(malloc(sizeof(Thread)+ + fullStackSize)); + if(base==NULL) return NULL; + + //At the top of thread memory allocate the Thread class with placement new + void *threadClass=base+(fullStackSize/sizeof(unsigned int)); + Thread *thread=new (threadClass) Thread(base,stacksize,defaultReent); + + if(thread->cReentrancyData==nullptr) + { + thread->~Thread(); + free(base); //Delete ALL thread memory + return NULL; + } + + //Fill watermark and stack + memset(base, WATERMARK_FILL, WATERMARK_LEN); + base+=WATERMARK_LEN/sizeof(unsigned int); + memset(base, STACK_FILL, fullStackSize-WATERMARK_LEN); + + //On some architectures some registers are saved on the stack, therefore + //initCtxsave *must* be called after filling the stack. + miosix_private::initCtxsave(thread->ctxsave,startfunc, + reinterpret_cast(thread),argv); + + if((options & JOINABLE)==0) thread->flags.IRQsetDetached(); + return thread; +} + +#ifdef WITH_PROCESSES + +void Thread::IRQhandleSvc(unsigned int svcNumber) +{ + if(cur->proc==kernel) errorHandler(UNEXPECTED); + if(svcNumber==SYS_USERSPACE) + { + const_cast(cur)->flags.IRQsetUserspace(true); + ::ctxsave=cur->userCtxsave; + //We know it's not the kernel, so the cast is safe + static_cast(cur->proc)->mpu.IRQenable(); + } else { + const_cast(cur)->flags.IRQsetUserspace(false); + ::ctxsave=cur->ctxsave; + MPUConfiguration::IRQdisable(); + } +} + +bool Thread::IRQreportFault(const miosix_private::FaultData& fault) +{ + if(const_cast(cur)->flags.isInUserspace()==false + || cur->proc==kernel) return false; + //We know it's not the kernel, so the cast is safe + static_cast(cur->proc)->fault=fault; + const_cast(cur)->flags.IRQsetUserspace(false); + ::ctxsave=cur->ctxsave; + MPUConfiguration::IRQdisable(); + return true; +} + +#endif //WITH_PROCESSES + +void Thread::threadLauncher(void *(*threadfunc)(void*), void *argv) +{ + void *result=0; + #ifdef __NO_EXCEPTIONS + result=threadfunc(argv); + #else //__NO_EXCEPTIONS + try { + result=threadfunc(argv); + } catch(std::exception& e) { + errorLog("***An exception propagated through a thread\n"); + errorLog("what():%s\n",e.what()); + } catch(...) { + errorLog("***An exception propagated through a thread\n"); + } + #endif //__NO_EXCEPTIONS + //Thread returned from its entry point, so delete it + + //Since the thread is running, it cannot be in the sleeping_list, so no need + //to remove it from the list + { + FastInterruptDisableLock lock; + const_cast(cur)->flags.IRQsetDeleted(); + + if(const_cast(cur)->flags.isDetached()==false) + { + //If thread is joinable, handle join + if(cur->joinData.waitingForJoin!=NULL) + { + //Wake thread + cur->joinData.waitingForJoin->flags.IRQsetJoinWait(false); + } + //Set result + cur->joinData.result=result; + } else { + //If thread is detached, memory can be deallocated immediately + exist_deleted=true; + } + } + Thread::yield();//Since the thread is now deleted, yield immediately. + //Will never reach here + errorHandler(UNEXPECTED); +} + +Thread *Thread::allocateIdleThread() +{ + //NOTE: this function is only called once before the kernel is started, so + //there are no concurrency issues, not even with interrupts + + // Create the idle and main thread + auto *idle=Thread::doCreate(idleThread,STACK_IDLE,NULL,Thread::DEFAULT,true); + if(idle==nullptr) errorHandler(OUT_OF_MEMORY); + + // cur must point to a valid thread, so we make it point to the the idle one + cur=idle; + return idle; +} + +struct _reent *Thread::getCReent() +{ + return getCurrentThread()->cReentrancyData; +} + +#ifdef WITH_PROCESSES + +miosix_private::SyscallParameters Thread::switchToUserspace() +{ + miosix_private::portableSwitchToUserspace(); + miosix_private::SyscallParameters result(cur->userCtxsave); + return result; +} + +Thread *Thread::createUserspace(void *(*startfunc)(void *), void *argv, + unsigned short options, Process *proc) +{ + Thread *thread=doCreate(startfunc,SYSTEM_MODE_PROCESS_STACK_SIZE,argv, + options,false); + if(thread==NULL) return NULL; + + unsigned int *base=thread->watermark; + try { + thread->userCtxsave=new unsigned int[CTXSAVE_SIZE]; + } catch(std::bad_alloc&) { + thread->~Thread(); + free(base); //Delete ALL thread memory + return NULL;//Error + } + + thread->proc=proc; + thread->flags.IRQsetWait(true); //Thread is not yet ready + + //Add thread to thread list + { + //Handling the list of threads, critical section is required + PauseKernelLock lock; + if(Scheduler::PKaddThread(thread,MAIN_PRIORITY)==false) + { + //Reached limit on number of threads + base=thread->watermark; + thread->~Thread(); + free(base); //Delete ALL thread memory + return NULL; + } + } + + return thread; +} + +void Thread::setupUserspaceContext(unsigned int entry, unsigned int *gotBase, + unsigned int ramImageSize) +{ + void *(*startfunc)(void*)=reinterpret_cast(entry); + unsigned int *ep=gotBase+ramImageSize/sizeof(int); + miosix_private::initCtxsave(cur->userCtxsave,startfunc,ep,0,gotBase); +} + +#endif //WITH_PROCESSES + +Thread::Thread(unsigned int *watermark, unsigned int stacksize, + bool defaultReent) : schedData(), flags(), savedPriority(0), + mutexLocked(0), mutexWaiting(0), watermark(watermark), + ctxsave(), stacksize(stacksize) +{ + joinData.waitingForJoin=NULL; + if(defaultReent) cReentrancyData=_GLOBAL_REENT; + else { + cReentrancyData=new _reent; + if(cReentrancyData) _REENT_INIT_PTR(cReentrancyData); + } + #ifdef WITH_PROCESSES + proc=kernel; + userCtxsave=nullptr; + #endif //WITH_PROCESSES +} + +Thread::~Thread() +{ + if(cReentrancyData && cReentrancyData!=_GLOBAL_REENT) + { + _reclaim_reent(cReentrancyData); + delete cReentrancyData; + } + #ifdef WITH_PROCESSES + if(userCtxsave) delete[] userCtxsave; + #endif //WITH_PROCESSES +} + +// +// class ThreadFlags +// + +void Thread::ThreadFlags::IRQsetWait(bool waiting) +{ + if(waiting) flags |= WAIT; else flags &= ~WAIT; + Scheduler::IRQwaitStatusHook(); +} + +void Thread::ThreadFlags::IRQsetJoinWait(bool waiting) +{ + if(waiting) flags |= WAIT_JOIN; else flags &= ~WAIT_JOIN; + Scheduler::IRQwaitStatusHook(); +} + +void Thread::ThreadFlags::IRQsetCondWait(bool waiting) +{ + if(waiting) flags |= WAIT_COND; else flags &= ~WAIT_COND; + Scheduler::IRQwaitStatusHook(); +} + +void Thread::ThreadFlags::IRQsetSleep(bool sleeping) +{ + if(sleeping) flags |= SLEEP; else flags &= ~SLEEP; + Scheduler::IRQwaitStatusHook(); +} + +void Thread::ThreadFlags::IRQsetDeleted() +{ + flags |= DELETED; + Scheduler::IRQwaitStatusHook(); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/kernel/kernel.h b/lib/miosix-kernel/miosix/kernel/kernel.h new file mode 100644 index 00000000..9aeb0745 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/kernel.h @@ -0,0 +1,1077 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#ifndef KERNEL_H +#define KERNEL_H + +//Include settings. +#include "config/miosix_settings.h" +#include "interfaces/portability.h" +#include "kernel/scheduler/sched_types.h" +#include "stdlib_integration/libstdcpp_integration.h" +#include +#include +#include + +// some pthread functions are friends of Thread +#include + +/** + * \namespace miosix + * All user available kernel functions, classes are inside this namespace. + */ +namespace miosix { + +/** + * \addtogroup Kernel + * \{ + */ + +/** + * Disable interrupts, if interrupts were enable prior to calling this function. + * + * Please note that starting from Miosix 1.51 disableInterrupts() and + * enableInterrupts() can be nested. You can therefore call disableInterrupts() + * multiple times as long as each call is matched by a call to + * enableInterrupts().
+ * This replaced disable_and_save_interrupts() and restore_interrupts() + * + * disableInterrupts() cannot be called within an interrupt routine, but can be + * called before the kernel is started (and does nothing in this case) + */ +void disableInterrupts(); + +/** + * Enable interrupts.
+ * Please note that starting from Miosix 1.51 disableInterrupts() and + * enableInterrupts() can be nested. You can therefore call disableInterrupts() + * multiple times as long as each call is matched by a call to + * enableInterrupts().
+ * This replaced disable_and_save_interrupts() and restore_interrupts() + * + * enableInterrupts() cannot be called within an interrupt routine, but can be + * called before the kernel is started (and does nothing in this case) + */ +void enableInterrupts(); + +/** + * Fast version of disableInterrupts().
+ * Despite faster, it has a couple of preconditions: + * - calls to fastDisableInterrupts() can't be nested + * - it can't be used in code that is called before the kernel is started + */ +inline void fastDisableInterrupts() +{ + miosix_private::doDisableInterrupts(); +} + +/** + * Fast version of enableInterrupts().
+ * Despite faster, it has a couple of preconditions: + * - calls to fastDisableInterrupts() can't be nested + * - it can't be used in code that is called before the kernel is started, + * because it will (incorreclty) lead to interrupts being enabled before the + * kernel is started + */ +inline void fastEnableInterrupts() +{ + miosix_private::doEnableInterrupts(); +} + +/** + * This class is a RAII lock for disabling interrupts. This call avoids + * the error of not reenabling interrupts since it is done automatically. + */ +class InterruptDisableLock +{ +public: + /** + * Constructor, disables interrupts. + */ + InterruptDisableLock() + { + disableInterrupts(); + } + + /** + * Destructor, reenables interrupts + */ + ~InterruptDisableLock() + { + enableInterrupts(); + } + +private: + //Unwanted methods + InterruptDisableLock(const InterruptDisableLock& l); + InterruptDisableLock& operator= (const InterruptDisableLock& l); +}; + +/** + * This class allows to temporarily re enable interrpts in a scope where + * they are disabled with an InterruptDisableLock.
+ * Example: + * \code + * + * //Interrupts enabled + * { + * InterruptDisableLock dLock; + * + * //Now interrupts disabled + * + * { + * InterruptEnableLock eLock(dLock); + * + * //Now interrupts back enabled + * } + * + * //Now interrupts again disabled + * } + * //Finally interrupts enabled + * \endcode + */ +class InterruptEnableLock +{ +public: + /** + * Constructor, enables back interrupts. + * \param l the InteruptDisableLock that disabled interrupts. Note that + * this parameter is not used internally. It is only required to prevent + * erroneous use of this class by making an instance of it without an + * active InterruptEnabeLock + */ + InterruptEnableLock(InterruptDisableLock& l) + { + (void)l; + enableInterrupts(); + } + + /** + * Destructor. + * Disable back interrupts. + */ + ~InterruptEnableLock() + { + disableInterrupts(); + } + +private: + //Unwanted methods + InterruptEnableLock(const InterruptEnableLock& l); + InterruptEnableLock& operator= (const InterruptEnableLock& l); +}; + +/** + * This class is a RAII lock for disabling interrupts. This call avoids + * the error of not reenabling interrupts since it is done automatically. + * As opposed to InterruptDisableLock, this version doesn't support nesting + */ +class FastInterruptDisableLock +{ +public: + /** + * Constructor, disables interrupts. + */ + FastInterruptDisableLock() + { + fastDisableInterrupts(); + } + + /** + * Destructor, reenables interrupts + */ + ~FastInterruptDisableLock() + { + fastEnableInterrupts(); + } + +private: + //Unwanted methods + FastInterruptDisableLock(const FastInterruptDisableLock& l); + FastInterruptDisableLock& operator= (const FastInterruptDisableLock& l); +}; + +/** + * This class allows to temporarily re enable interrpts in a scope where + * they are disabled with an FastInterruptDisableLock. + */ +class FastInterruptEnableLock +{ +public: + /** + * Constructor, enables back interrupts. + * \param l the InteruptDisableLock that disabled interrupts. Note that + * this parameter is not used internally. It is only required to prevent + * erroneous use of this class by making an instance of it without an + * active InterruptEnabeLock + */ + FastInterruptEnableLock(FastInterruptDisableLock& l) + { + (void)l; + fastEnableInterrupts(); + } + + /** + * Destructor. + * Disable back interrupts. + */ + ~FastInterruptEnableLock() + { + fastDisableInterrupts(); + } + +private: + //Unwanted methods + FastInterruptEnableLock(const FastInterruptEnableLock& l); + FastInterruptEnableLock& operator= (const FastInterruptEnableLock& l); +}; + +/** + * Pause the kernel.
Interrupts will continue to occur, but no preemption is + * possible. Call to this function are cumulative: if you call pauseKernel() + * two times, you need to call restartKernel() two times.
Pausing the kernel + * must be avoided if possible because it is easy to cause deadlock. Calling + * file related functions (fopen, Directory::open() ...), serial port related + * functions (printf ...) or kernel functions that cannot be called when the + * kernel is paused will cause deadlock. Therefore, if possible, it is better to + * use a Mutex instead of pausing the kernel
This function is safe to be + * called even before the kernel is started. In this case it has no effect. + */ +void pauseKernel(); + +/** + * Restart the kernel.
This function will yield immediately if a tick has + * been missed. Since calls to pauseKernel() are cumulative, if you call + * pauseKernel() two times, you need to call restartKernel() two times.
+ * This function is safe to be called even before the kernel is started. In this + * case it has no effect. + */ +void restartKernel(); + +/** + * \return true if interrupts are enabled + */ +bool areInterruptsEnabled(); + +/** + * This class is a RAII lock for pausing the kernel. This call avoids + * the error of not restarting the kernel since it is done automatically. + */ +class PauseKernelLock +{ +public: + /** + * Constructor, pauses the kernel. + */ + PauseKernelLock() + { + pauseKernel(); + } + + /** + * Destructor, restarts the kernel + */ + ~PauseKernelLock() + { + restartKernel(); + } + +private: + //Unwanted methods + PauseKernelLock(const PauseKernelLock& l); + PauseKernelLock& operator= (const PauseKernelLock& l); +}; + +/** + * This class allows to temporarily restart kernel in a scope where it is + * paused with an InterruptDisableLock.
+ * Example: + * \code + * + * //Kernel started + * { + * PauseKernelLock dLock; + * + * //Now kernel paused + * + * { + * RestartKernelLock eLock(dLock); + * + * //Now kernel back started + * } + * + * //Now kernel again paused + * } + * //Finally kernel started + * \endcode + */ +class RestartKernelLock +{ +public: + /** + * Constructor, restarts kernel. + * \param l the PauseKernelLock that disabled interrupts. Note that + * this parameter is not used internally. It is only required to prevent + * erroneous use of this class by making an instance of it without an + * active PauseKernelLock + */ + RestartKernelLock(PauseKernelLock& l) + { + (void)l; + restartKernel(); + } + + /** + * Destructor. + * Disable back interrupts. + */ + ~RestartKernelLock() + { + pauseKernel(); + } + +private: + //Unwanted methods + RestartKernelLock(const RestartKernelLock& l); + RestartKernelLock& operator= (const RestartKernelLock& l); +}; + +/** + * \internal + * Start the kernel.
There is no way to stop the kernel once it is + * started, except a (software or hardware) system reset.
+ * Calls errorHandler(OUT_OF_MEMORY) if there is no heap to create the idle + * thread. If the function succeds in starting the kernel, it never returns; + * otherwise it will call errorHandler(OUT_OF_MEMORY) and then return + * immediately. startKernel() must not be called when the kernel is already + * started. + */ +void startKernel(); + +/** + * Return true if kernel is running, false if it is not started, or paused.
+ * Warning: disabling/enabling interrupts does not affect the result returned by + * this function. + * \return true if kernel is running (started && not paused) + */ +bool isKernelRunning(); + +/** + * Returns the current kernel tick.
Can be called also with interrupts + * disabled and/or kernel paused. + * \return current kernel tick + */ +long long getTick(); + +//Forwrd declaration +struct SleepData; +class MemoryProfiling; +class Mutex; +class ConditionVariable; +#ifdef WITH_PROCESSES +class ProcessBase; +#endif //WITH_PROCESSES + +/** + * This class represents a thread. It has methods for creating, deleting and + * handling threads.
It has private constructor and destructor, since memory + * for a thread is handled by the kernel.
To create a thread use the static + * producer method create().
+ * Methods that have an effect on the current thread, that is, the thread that + * is calling the method are static.
+ * Calls to non static methods must be done with care, because a thread can + * terminate at any time. For example, if you call wakeup() on a terminated + * thread, the behavior is undefined. + */ +class Thread +{ +public: + + /** + * Thread options, can be passed to Thread::create to set additional options + * of the thread. + * More options can be specified simultaneously by ORing them together. + * The DEFAULT option indicates the default thread creation. + */ + enum Options + { + DEFAULT=0, ///< Default thread options + JOINABLE=1<<0 ///< Thread is joinable instead of detached + }; + + /** + * Producer method, creates a new thread. + * \param startfunc the entry point function for the thread + * \param stacksize size of thread stack, its minimum is the constant + * STACK_MIN. + * The size of the stack must be divisible by 4, otherwise it will be + * rounded to a number divisible by 4. + * \param priority the thread's priority, between 0 (lower) and + * PRIORITY_MAX-1 (higher) + * \param argv a void* pointer that is passed as pararmeter to the entry + * point function + * \param options thread options, such ad Thread::JOINABLE + * \return a reference to the thread created, that can be used, for example, + * to delete it, or NULL in case of errors. + * + * Can be called when the kernel is paused. + */ + static Thread *create(void *(*startfunc)(void *), unsigned int stacksize, + Priority priority=Priority(), void *argv=NULL, + unsigned short options=DEFAULT); + + /** + * Same as create(void (*startfunc)(void *), unsigned int stacksize, + * Priority priority=1, void *argv=NULL) + * but in this case the entry point of the thread returns a void* + * \param startfunc the entry point function for the thread + * \param stacksize size of thread stack, its minimum is the constant + * STACK_MIN. + * The size of the stack must be divisible by 4, otherwise it will be + * rounded to a number divisible by 4. + * \param priority the thread's priority, between 0 (lower) and + * PRIORITY_MAX-1 (higher) + * \param argv a void* pointer that is passed as pararmeter to the entry + * point function + * \param options thread options, such ad Thread::JOINABLE + * \return a reference to the thread created, that can be used, for example, + * to delete it, or NULL in case of errors. + */ + static Thread *create(void (*startfunc)(void *), unsigned int stacksize, + Priority priority=Priority(), void *argv=NULL, + unsigned short options=DEFAULT); + + /** + * When called, suggests the kernel to pause the current thread, and run + * another one. + *
CANNOT be called when the kernel is paused. + */ + static void yield(); + + /** + * This method needs to be called periodically inside the thread's main + * loop. + * \return true if somebody outside the thread called terminate() on this + * thread. + * + * If it returns true the thread must free all resources and terminate by + * returning from its main function. + *
Can be called when the kernel is paused. + */ + static bool testTerminate(); + + /** + * Put the thread to sleep for a number of milliseconds.
The actual + * precision depends on the kernel tick used. If the specified wait time is + * lower than the tick accuracy, the thread will be put to sleep for one + * tick.
Maximum sleep time is (2^32-1) / TICK_FREQ. If a sleep time + * higher than that value is specified, the behaviour is undefined. + * \param ms the number of millisecond. If it is ==0 this method will + * return immediately + * + * CANNOT be called when the kernel is paused. + */ + static void sleep(unsigned int ms); + + /** + * Put the thread to sleep until the specified absolute time is reached. + * If the time is in the past, returns immediately. + * To make a periodic thread, this is the recomended way + * \code + * void periodicThread() + * { + * //Run every 90 milliseconds + * const int period=static_cast(TICK_FREQ*0.09); + * long long tick=getTick(); + * for(;;) + * { + * //Do work + * tick+=period; + * Thread::sleepUntil(tick); + * } + * } + * \endcode + * \param absoluteTime when to wake up + * + * CANNOT be called when the kernel is paused. + */ + static void sleepUntil(long long absoluteTime); + + /** + * Return a pointer to the Thread class of the current thread. + * \return a pointer to the current thread. + * + * Can be called when the kernel is paused. + * Returns a valid pointer also if called before the kernel is started. + */ + static Thread *getCurrentThread(); + + /** + * Check if a thread exists + * \param p thread to check + * \return true if thread exists, false if does not exist or has been + * deleted. A joinable thread is considered existing until it has been + * joined, even if it returns from its entry point (unless it is detached + * and terminates). + * + * Can be called when the kernel is paused. + */ + static bool exists(Thread *p); + + /** + * Returns the priority of a thread.
+ * To get the priority of the current thread use: + * \code Thread::getCurrentThread()->getPriority(); \endcode + * If the thread is currently locking one or more mutexes, this member + * function returns the current priority, which can be higher than the + * original priority due to priority inheritance. + * \return current priority of the thread + * + * Can be called when the kernel is paused. + */ + Priority getPriority(); + + /** + * Set the priority of this thread.
+ * This member function changed from previous Miosix versions since it is + * now static. This implies a thread can no longer set the priority of + * another thread. + * \param pr desired priority. Must be 0<=prThread termination is implemented like this to give + * time to a thread to deallocate resources, close files... before + * terminating.
Can be called when the kernel is paused. + */ + void terminate(); + + /** + * This method stops the thread until another thread calls wakeup() on this + * thread.
Calls to wait are not cumulative. If wait() is called two + * times, only one call to wakeup() is needed to wake the thread. + *
CANNOT be called when the kernel is paused. + */ + static void wait(); + + /** + * Wakeup a thread. + *
CANNOT be called when the kernel is paused. + */ + void wakeup(); + + /** + * Wakeup a thread. + *
Can be called when the kernel is paused. + */ + void PKwakeup(); + + /** + * Detach the thread if it was joinable, otherwise do nothing.
+ * If called on a deleted joinable thread on which join was not yet called, + * it allows the thread's memory to be deallocated.
+ * If called on a thread that is not yet deleted, the call detaches the + * thread without deleting it. + * If called on an already detached thread, it has undefined behaviour. + */ + void detach(); + + /** + * \return true if the thread is detached + */ + bool isDetached() const; + + /** + * Wait until a joinable thread is terminated.
+ * If the thread already terminated, this function returns immediately.
+ * Calling join() on the same thread multiple times, from the same or + * multiple threads is not recomended, but in the current implementation + * the first call will wait for join, and the other will return false.
+ * Trying to join the thread join is called in returns false, but must be + * avoided.
+ * Calling join on a detached thread might cause undefined behaviour. + * \param result If the entry point function of the thread to join returns + * void *, the return value of the entry point is stored here, otherwise + * the content of this variable is undefined. If NULL is passed as result + * the return value will not be stored. + * \return true on success, false on failure + */ + bool join(void** result=NULL); + + /** + * Same as get_current_thread(), but meant to be used insida an IRQ, when + * interrupts are disabled or when the kernel is paused. + */ + static Thread *IRQgetCurrentThread(); + + /** + * Same as getPriority(), but meant to be used inside an IRQ, when + * interrupts are disabled or when the kernel is paused. + */ + Priority IRQgetPriority(); + + /** + * Same as wait(), but is meant to be used only inside an IRQ or when + * interrupts are disabled.
+ * Note: this method is meant to put the current thread in wait status in a + * piece of code where interrupts are disbled; it returns immediately, so + * the user is responsible for re-enabling interrupts and calling yield to + * effectively put the thread in wait status. + * + * \code + * disableInterrupts(); + * ... + * Thread::IRQwait();//Return immediately + * enableInterrupts(); + * Thread::yield();//After this, thread is in wait status + * \endcode + */ + static void IRQwait(); + + /** + * Same as wakeup(), but is meant to be used only inside an IRQ or when + * interrupts are disabled. + */ + void IRQwakeup(); + + /** + * Same as exists() but is meant to be called only inside an IRQ or when + * interrupts are disabled. + */ + static bool IRQexists(Thread *p); + + /** + * \internal + * This method is only meant to implement functions to check the available + * stack in a thread. Returned pointer is constant because modifying the + * stack through it must be avoided. + * \return pointer to bottom of stack of current thread. + */ + static const unsigned int *getStackBottom(); + + /** + * \internal + * \return the size of the stack of the current thread. + */ + static int getStackSize(); + + #ifdef WITH_PROCESSES + + /** + * \return the process associated with the thread + */ + ProcessBase *getProcess() { return proc; } + + /** + * \internal + * Can only be called inside an IRQ, its use is to switch a thread between + * userspace/kernelspace and back to perform context switches + */ + static void IRQhandleSvc(unsigned int svcNumber); + + /** + * \internal + * Can only be called inside an IRQ, its use is to report a fault so that + * in case the fault has occurred within a process while it was executing + * in userspace, the process can be terminated. + * \param fault data about the occurred fault + * \return true if the fault was caused by a process, false otherwise. + */ + static bool IRQreportFault(const miosix_private::FaultData& fault); + + #endif //WITH_PROCESSES + +private: + //Unwanted methods + Thread(const Thread& p);///< No public copy constructor + Thread& operator = (const Thread& p);///< No publc operator = + + class ThreadFlags + { + public: + /** + * Constructor, sets flags to default. + */ + ThreadFlags() : flags(0) {} + + /** + * Set the wait flag of the thread. + * Can only be called with interrupts disabled or within an interrupt. + * \param waiting if true the flag will be set, otherwise cleared + */ + void IRQsetWait(bool waiting); + + /** + * Set the wait_join flag of the thread. + * Can only be called with interrupts disabled or within an interrupt. + * \param waiting if true the flag will be set, otherwise cleared + */ + void IRQsetJoinWait(bool waiting); + + /** + * Set wait_cond flag of the thread. + * Can only be called with interrupts disabled or within an interrupt. + * \param waiting if true the flag will be set, otherwise cleared + */ + void IRQsetCondWait(bool waiting); + + /** + * Set the sleep flag of the thread. + * Can only be called with interrupts disabled or within an interrupt. + * \param sleeping if true the flag will be set, otherwise cleared + */ + void IRQsetSleep(bool sleeping); + + /** + * Set the deleted flag of the thread. This flag can't be cleared. + * Can only be called with interrupts disabled or within an interrupt. + */ + void IRQsetDeleted(); + + /** + * Set the sleep flag of the thread. This flag can't be cleared. + * Can only be called with interrupts disabled or within an interrupt. + */ + void IRQsetDeleting() + { + flags |= DELETING; + } + + /** + * Set the detached flag. This flag can't be cleared. + * Can only be called with interrupts disabled or within an interrupt. + */ + void IRQsetDetached() + { + flags |= DETACHED; + } + + /** + * Set the userspace flag of the thread. + * Can only be called with interrupts disabled or within an interrupt. + * \param sleeping if true the flag will be set, otherwise cleared + */ + void IRQsetUserspace(bool userspace) + { + if(userspace) flags |= USERSPACE; else flags &= ~USERSPACE; + } + + /** + * \return true if the wait flag is set + */ + bool isWaiting() const { return flags & WAIT; } + + /** + * \return true if the sleep flag is set + */ + bool isSleeping() const { return flags & SLEEP; } + + /** + * \return true if the deleted and the detached flags are set + */ + bool isDeleted() const { return (flags & 0x14)==0x14; } + + /** + * \return true if the thread has been deleted, but its resources cannot + * be reclaimed because it has not yet been joined + */ + bool isDeletedJoin() const { return flags & DELETED; } + + /** + * \return true if the deleting flag is set + */ + bool isDeleting() const { return flags & DELETING; } + + /** + * \return true if the thread is in the ready status + */ + bool isReady() const { return (flags & 0x67)==0; } + + /** + * \return true if the thread is detached + */ + bool isDetached() const { return flags & DETACHED; } + + /** + * \return true if the thread is waiting a join + */ + bool isWaitingJoin() const { return flags & WAIT_JOIN; } + + /** + * \return true if the thread is waiting on a condition variable + */ + bool isWaitingCond() const { return flags & WAIT_COND; } + + /** + * \return true if the thread is running unprivileged inside a process. + */ + bool isInUserspace() const { return flags & USERSPACE; } + + private: + ///\internal Thread is in the wait status. A call to wakeup will change + ///this + static const unsigned int WAIT=1<<0; + + ///\internal Thread is sleeping. + static const unsigned int SLEEP=1<<1; + + ///\internal Thread is deleted. It will continue to exist until the + ///idle thread deallocates its resources + static const unsigned int DELETED=1<<2; + + ///\internal Somebody outside the thread asked this thread to delete + ///itself.
This will make Thread::testTerminate() return true. + static const unsigned int DELETING=1<<3; + + ///\internal Thread is detached + static const unsigned int DETACHED=1<<4; + + ///\internal Thread is waiting for a join + static const unsigned int WAIT_JOIN=1<<5; + + ///\internal Thread is waiting on a condition variable + static const unsigned int WAIT_COND=1<<6; + + ///\internal Thread is running in userspace + static const unsigned int USERSPACE=1<<7; + + unsigned short flags;///<\internal flags are stored here + }; + + #ifdef WITH_PROCESSES + + /** + * \internal + * Causes a thread belonging to a process to switch to userspace, and + * execute userspace code. This function returns when the process performs + * a syscall or faults. + * \return the syscall parameters used to serve the system call. + */ + static miosix_private::SyscallParameters switchToUserspace(); + + /** + * Create a thread to be used inside a process. The thread is created in + * WAIT status, a wakeup() on it is required to actually start it. + * \param startfunc entry point + * \param argv parameter to be passed to the entry point + * \param options thread options + * \param proc process to which this thread belongs + */ + static Thread *createUserspace(void *(*startfunc)(void *), + void *argv, unsigned short options, Process *proc); + + /** + * Setup the userspace context of the thread, so that it can be later + * switched to userspace. Must be called only once for each thread instance + * \param entry userspace entry point + * \param gotBase base address of the GOT, also corresponding to the start + * of the RAM image of the process + * \param ramImageSize size of the process ram image + */ + static void setupUserspaceContext(unsigned int entry, unsigned int *gotBase, + unsigned int ramImageSize); + + #endif //WITH_PROCESSES + + /** + * Constructor, initializes thread data. + * \param watermark pointer to watermark area + * \param stacksize thread's stack size + * \param defaultReent true if the global reentrancy structure is to be used + */ + Thread(unsigned int *watermark, unsigned int stacksize, bool defaultReent); + + /** + * Destructor + */ + ~Thread(); + + /** + * Helper function to initialize a Thread + * \param startfunc entry point function + * \param stacksize stack size for the thread + * \param argv argument passed to the thread entry point + * \param options thread options + * \param defaultReent true if the default C reentrancy data should be used + * \return a pointer to a thread, or NULL in case there are not enough + * resources to create one. + */ + static Thread *doCreate(void *(*startfunc)(void *), unsigned int stacksize, + void *argv, unsigned short options, bool defaultReent); + + /** + * Thread launcher, all threads start from this member function, which calls + * the user specified entry point. When the entry point function returns, + * it marks the thread as deleted so that the idle thread can dellocate it. + * If exception handling is enebled, this member function also catches any + * exception that propagates through the entry point. + * \param threadfunc pointer to the entry point function + * \param argv argument passed to the entry point + */ + static void threadLauncher(void *(*threadfunc)(void*), void *argv); + + /** + * Allocates the idle thread and makes cur point to it + * Can only be called before the kernel is started, is called exactly once + * so that getCurrentThread() always returns a pointer to a valid thread or + * by startKernel to create the idle thread, whichever comes first. + * \return the newly allocated idle thread + */ + static Thread *allocateIdleThread(); + + /** + * \return the C reentrancy structure of the currently running thread + */ + static struct _reent *getCReent(); + + //Thread data + SchedulerData schedData; ///< Scheduler data, only used by class Scheduler + ThreadFlags flags;///< thread status + ///Saved priority. Its value is relevant only if mutexLockedCount>0; it + ///stores the value of priority that this thread will have when it unlocks + ///all mutexes. This is because when a thread locks a mutex its priority + ///can change due to priority inheritance. + Priority savedPriority; + ///List of mutextes locked by this thread + Mutex *mutexLocked; + ///If the thread is waiting on a Mutex, mutexWaiting points to that Mutex + Mutex *mutexWaiting; + unsigned int *watermark;///< pointer to watermark area + unsigned int ctxsave[CTXSAVE_SIZE];///< Holds cpu registers during ctxswitch + unsigned int stacksize;///< Contains stack size + ///This union is used to join threads. When the thread to join has not yet + ///terminated and no other thread called join it contains (Thread *)NULL, + ///when a thread calls join on this thread it contains the thread waiting + ///for the join, and when the thread terminated it contains (void *)result + union + { + Thread *waitingForJoin;/// +{ +public: + /** + * \param a first thread to compare + * \param b second thread to compare + * \return true if a->getPriority() < b->getPriority() + * + * Can be called when the kernel is paused. or with interrupts disabled + */ + bool operator() (Thread* a, Thread *b) + { + return a->getPriority() < b->getPriority(); + } +}; + +/** + * \internal + * \struct Sleep_data + * This struct is used to make a list of sleeping threads. + * It is used by the kernel, and should not be used by end users. + */ +struct SleepData +{ + ///\internal Thread that is sleeping + Thread *p; + + ///\internal When this number becomes equal to the kernel tick, + ///the thread will wake + long long wakeup_time; + + SleepData *next;///<\internal Next thread in the list +}; + +/** + * \} + */ + +} //namespace miosix + +#endif //KERNEL_H diff --git a/lib/miosix-kernel/miosix/kernel/logging.h b/lib/miosix-kernel/miosix/kernel/logging.h new file mode 100644 index 00000000..4b0ed676 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/logging.h @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef LOGGING_H +#define LOGGING_H + +#include "config/miosix_settings.h" +#include "filesystem/console/console_device.h" +#include +#include + +/** + * Print boot logs. Contrary to (i)printf(), this can be disabled in + * miosix_settings.h if boot logs are not wanted. Can only be called when the + * kernel is running. + * \param fmt format string + */ +#ifdef WITH_BOOTLOG +inline void bootlog(const char *fmt, ...) +{ + va_list arg; + va_start(arg,fmt); + viprintf(fmt,arg); + va_end(arg); +} +#else //WITH_BOOTLOG +#define bootlog(x,...) ; +#endif //WITH_BOOTLOG + +/** + * Print boot logs. Can only be called when the kernel is not yet running or + * paused, or within an IRQ. + * \param string to print + */ +#ifdef WITH_BOOTLOG +inline void IRQbootlog(const char *string) +{ + miosix::DefaultConsole::instance().IRQget()->IRQwrite(string); +} +#else //WITH_BOOTLOG +#define IRQbootlog(x) ; +#endif //WITH_BOOTLOG + +/** + * Print error logs. Cotrary to (i)printf(), this can be disabled in + * miosix_settings.h if boot logs are not wanted. Can only be called when the + * kernel is running. + * \param fmt format string + */ +#ifdef WITH_ERRLOG +inline void errorLog(const char *fmt, ...) +{ + va_list arg; + va_start(arg,fmt); + viprintf(fmt,arg); + va_end(arg); +} +#else //WITH_ERRLOG +#define errorLog(x,...) ; +#endif //WITH_ERRLOG + +/** + * Print error logs. Can only be called when the kernel is not yet running or + * paused, or within an IRQ. + * \param string to print + */ +#ifdef WITH_ERRLOG +inline void IRQerrorLog(const char *string) +{ + miosix::DefaultConsole::instance().IRQget()->IRQwrite(string); +} +#else //WITH_ERRLOG +#define IRQerrorLog(x) ; +#endif //WITH_ERRLOG + +#endif /* LOGGING_H */ diff --git a/lib/miosix-kernel/miosix/kernel/process.cpp b/lib/miosix-kernel/miosix/kernel/process.cpp new file mode 100644 index 00000000..4a18de97 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/process.cpp @@ -0,0 +1,522 @@ +/*************************************************************************** + * Copyright (C) 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sync.h" +#include "process_pool.h" +#include "process.h" +#include "SystemMap.h" + +using namespace std; + +#ifdef WITH_PROCESSES + +namespace miosix { + +/** + * Used to check if a pointer passed from userspace is aligned + */ +static bool aligned(void *x) { return (reinterpret_cast(x) & 0b11)==0; } + +/** + * This class contains information on all the processes in the system + */ +class Processes +{ +public: + /** + * \return the instance of this class (singleton) + */ + static Processes& instance(); + + ///Used to assign a new pid to a process + pid_t pidCounter; + ///Maps the pid to the Process instance. Includes zombie processes + map processes; + ///Uset to guard access to processes and pidCounter + Mutex procMutex; + ///Used to wait on process termination + ConditionVariable genericWaiting; + +private: + Processes() + { + ProcessBase *kernel=Thread::getCurrentThread()->getProcess(); + assert(kernel->getPid()==0); + processes[0]=kernel; + } + Processes(const Processes&); + Processes& operator=(const Processes&); +}; + +Processes& Processes::instance() +{ + static Processes singleton; + return singleton; +} + +// +// class Process +// + +pid_t Process::create(const ElfProgram& program) +{ + Processes& p=Processes::instance(); + ProcessBase *parent=Thread::getCurrentThread()->proc; + unique_ptr proc(new Process(program)); + { + Lock l(p.procMutex); + proc->pid=getNewPid(); + proc->ppid=parent->pid; + parent->childs.push_back(proc.get()); + p.processes[proc->pid]=proc.get(); + } + Thread *thr; + thr=Thread::createUserspace(Process::start,0,Thread::DEFAULT,proc.get()); + if(thr==0) + { + Lock l(p.procMutex); + p.processes.erase(proc->pid); + parent->childs.remove(proc.get()); + throw runtime_error("Thread creation failed"); + } + //Cannot throw bad_alloc due to the reserve in Process's constructor. + //This ensures we will never be in the uncomfortable situation where a + //thread has already been created but there's no memory to list it + //among the threads of a process + proc->threads.push_back(thr); + thr->wakeup(); //Actually start the thread, now that everything is set up + pid_t result=proc->pid; + proc.release(); //Do not delete the pointer + return result; +} + +pid_t Process::getppid(pid_t proc) +{ + Processes& p=Processes::instance(); + Lock l(p.procMutex); + map::iterator it=p.processes.find(proc); + if(it==p.processes.end()) return -1; + return it->second->ppid; +} + +pid_t Process::waitpid(pid_t pid, int* exit, int options) +{ + Processes& p=Processes::instance(); + Lock l(p.procMutex); + ProcessBase *self=Thread::getCurrentThread()->proc; + if(pid<=0) + { + //Wait for a generic child process + if(self->zombies.empty() && (options & WNOHANG)) return 0; + while(self->zombies.empty()) + { + if(self->childs.empty()) return -1; + p.genericWaiting.wait(l); + } + Process *joined=self->zombies.front(); + self->zombies.pop_front(); + p.processes.erase(joined->pid); + if(joined->waitCount!=0) errorHandler(UNEXPECTED); + if(exit!=0) *exit=joined->exitCode; + pid_t result=joined->pid; + delete joined; + return result; + } else { + //Wait on a specific child process + map::iterator it=p.processes.find(pid); + if(it==p.processes.end() || it->second->ppid!=self->pid + || pid==self->pid) return -1; + //Since the case when pid==0 has been singled out, this cast is safe + Process *joined=static_cast(it->second); + if(joined->zombie==false) + { + //Process hasn't terminated yet + if(options & WNOHANG) return 0; + joined->waitCount++; + joined->waiting.wait(l); + joined->waitCount--; + if(joined->waitCount<0 || joined->zombie==false) + errorHandler(UNEXPECTED); + } + pid_t result=-1; + if(joined->waitCount==0) + { + result=joined->pid; + if(exit!=0) *exit=joined->exitCode; + self->zombies.remove(joined); + p.processes.erase(joined->pid); + delete joined; + } + return result; + } +} + +Process::~Process() {} + +Process::Process(const ElfProgram& program) : program(program), waitCount(0), + zombie(false) +{ + //This is required so that bad_alloc can never be thrown when the first + //thread of the process will be stored in this vector + threads.reserve(1); + //Done here so if not enough memory the new process is not even created + image.load(program); + unsigned int elfSize=program.getElfSize(); + unsigned int roundedSize=elfSize; + if(elfSize(&_elf_pool_start); + unsigned int *end=reinterpret_cast(&_elf_pool_end); + unsigned int elfPoolSize=(end-start)*sizeof(int); + elfPoolSize=MPUConfiguration::roundSizeForMPU(elfPoolSize); + mpu=MPUConfiguration(start,elfPoolSize, + image.getProcessBasePointer(),image.getProcessImageSize()); +// mpu=MPUConfiguration(program.getElfBase(),roundedSize, +// image.getProcessBasePointer(),image.getProcessImageSize()); +} + +void *Process::start(void *argv) +{ + //This function is never called with a kernel thread, so the cast is safe + Process *proc=static_cast(Thread::getCurrentThread()->proc); + if(proc==0) errorHandler(UNEXPECTED); + unsigned int entry=proc->program.getEntryPoint(); + Thread::setupUserspaceContext(entry,proc->image.getProcessBasePointer(), + proc->image.getProcessImageSize()); + bool running=true; + do { + miosix_private::SyscallParameters sp=Thread::switchToUserspace(); + if(proc->fault.faultHappened()) + { + running=false; + proc->exitCode=SIGSEGV; //Segfault + #ifdef WITH_ERRLOG + iprintf("Process %d terminated due to a fault\n" + "* Code base address was 0x%x\n" + "* Data base address was %p\n",proc->pid, + proc->program.getElfBase(), + proc->image.getProcessBasePointer()); + proc->mpu.dumpConfiguration(); + proc->fault.print(); + #endif //WITH_ERRLOG + } else running=proc->handleSvc(sp); + if(Thread::testTerminate()) running=false; + } while(running); + { + Processes& p=Processes::instance(); + Lock l(p.procMutex); + proc->zombie=true; + list::iterator it; + for(it=proc->childs.begin();it!=proc->childs.end();++it) (*it)->ppid=0; + for(it=proc->zombies.begin();it!=proc->zombies.end();++it) (*it)->ppid=0; + ProcessBase *kernel=p.processes[0]; + kernel->childs.splice(kernel->childs.begin(),proc->childs); + kernel->zombies.splice(kernel->zombies.begin(),proc->zombies); + + map::iterator it2=p.processes.find(proc->ppid); + if(it2==p.processes.end()) errorHandler(UNEXPECTED); + it2->second->childs.remove(proc); + if(proc->waitCount>0) proc->waiting.broadcast(); + else { + it2->second->zombies.push_back(proc); + p.genericWaiting.broadcast(); + } + } + return 0; +} + +bool Process::handleSvc(miosix_private::SyscallParameters sp) +{ + try { + switch(sp.getSyscallId()) + { + case SYS_EXIT: + { + exitCode=(sp.getFirstParameter() & 0xff)<<8; + return false; + } + case SYS_WRITE: + { + int fd=sp.getFirstParameter(); + void *ptr=reinterpret_cast(sp.getSecondParameter()); + size_t size=sp.getThirdParameter(); + if(mpu.withinForReading(ptr,size)) + { + ssize_t result=fileTable.write(fd,ptr,size); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_READ: + { + int fd=sp.getFirstParameter(); + void *ptr=reinterpret_cast(sp.getSecondParameter()); + size_t size=sp.getThirdParameter(); + if(mpu.withinForWriting(ptr,size)) + { + ssize_t result=fileTable.read(fd,ptr,size); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_USLEEP: + { + sp.setReturnValue(usleep(sp.getFirstParameter())); + break; + } + case SYS_OPEN: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + int flags=sp.getSecondParameter(); + if(mpu.withinForReading(str)) + { + int fd=fileTable.open(str,flags, + (flags & O_CREAT) ? sp.getThirdParameter() : 0); + sp.setReturnValue(fd); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_CLOSE: + { + int result=fileTable.close(sp.getFirstParameter()); + sp.setReturnValue(result); + break; + } + case SYS_LSEEK: + { + //FIXME: need to pass and return a 64 bit parameter, + //now it is truncated to 32 bit but this is wrong + off_t result=fileTable.lseek(sp.getFirstParameter(), + sp.getSecondParameter(),sp.getThirdParameter()); + sp.setReturnValue(result); + break; + } + case SYS_SYSTEM: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + if(mpu.withinForReading(str)) + { + std::pair res; + res=SystemMap::instance().getElfProgram(str); + if(res.first==0 || res.second==0) + { + sp.setReturnValue(-1); + } else { + ElfProgram program(res.first,res.second); + int ret=0; + pid_t child=Process::create(program); + Process::waitpid(child,&ret,0); + sp.setReturnValue(WEXITSTATUS(ret)); + } + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_FSTAT: + { + struct stat *pstat; + pstat=reinterpret_cast(sp.getSecondParameter()); + if(mpu.withinForWriting(pstat,sizeof(struct stat)) && aligned(pstat)) + { + int result=fileTable.fstat(sp.getFirstParameter(),pstat); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_ISATTY: + { + int result=fileTable.isatty(sp.getFirstParameter()); + sp.setReturnValue(result); + break; + } + case SYS_STAT: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + struct stat *pstat; + pstat=reinterpret_cast(sp.getSecondParameter()); + if(mpu.withinForReading(str) && + mpu.withinForWriting(pstat,sizeof(struct stat)) && aligned(pstat)) + { + int result=fileTable.stat(str,pstat); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + } + case SYS_LSTAT: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + struct stat *pstat; + pstat=reinterpret_cast(sp.getSecondParameter()); + if(mpu.withinForReading(str) && + mpu.withinForWriting(pstat,sizeof(struct stat)) && aligned(pstat)) + { + int result=fileTable.lstat(str,pstat); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + } + case SYS_FCNTL: + { + int result=fileTable.fcntl(sp.getFirstParameter(), + sp.getSecondParameter(),sp.getThirdParameter()); + sp.setReturnValue(result); + break; + } + case SYS_IOCTL: + { + //TODO: need a way to validate ARG + break; + } + case SYS_GETDENTS: + { + int fd=sp.getFirstParameter(); + void *ptr=reinterpret_cast(sp.getSecondParameter()); + size_t size=sp.getThirdParameter(); + if(mpu.withinForWriting(ptr,size)) + { + int result=fileTable.getdents(fd,ptr,size); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_GETCWD: + { + char *buf=reinterpret_cast(sp.getFirstParameter()); + size_t size=sp.getSecondParameter(); + if(mpu.withinForWriting(buf,size)) + { + int result=fileTable.getcwd(buf,size); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_CHDIR: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + if(mpu.withinForReading(str)) + { + int result=fileTable.chdir(str); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_MKDIR: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + if(mpu.withinForReading(str)) + { + int result=fileTable.mkdir(str,sp.getSecondParameter()); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_RMDIR: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + if(mpu.withinForReading(str)) + { + int result=fileTable.rmdir(str); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_UNLINK: + { + const char *str; + str=reinterpret_cast(sp.getFirstParameter()); + if(mpu.withinForReading(str)) + { + int result=fileTable.unlink(str); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + case SYS_RENAME: + { + const char *oldName, *newName; + oldName=reinterpret_cast(sp.getFirstParameter()); + newName=reinterpret_cast(sp.getSecondParameter()); + if(mpu.withinForReading(oldName) && + mpu.withinForReading(newName)) + { + int result=fileTable.rename(oldName,newName); + sp.setReturnValue(result); + } else sp.setReturnValue(-EFAULT); + break; + } + default: + exitCode=SIGSYS; //Bad syscall + #ifdef WITH_ERRLOG + iprintf("Unexpected syscall number %d\n",sp.getSyscallId()); + #endif //WITH_ERRLOG + return false; + } + } catch(exception& e) { + sp.setReturnValue(-ENOMEM); + } + return true; +} + +pid_t Process::getNewPid() +{ + Processes& p=Processes::instance(); + for(;;p.pidCounter++) + { + if(p.pidCounter<0) p.pidCounter=1; + if(p.pidCounter==0) continue; //Zero is not a valid pid + map::iterator it=p.processes.find(p.pidCounter); + if(it!=p.processes.end()) continue; //Pid number already used + return p.pidCounter++; + } +} + +} //namespace miosix + +#endif //WITH_PROCESSES diff --git a/lib/miosix-kernel/miosix/kernel/process.h b/lib/miosix-kernel/miosix/kernel/process.h new file mode 100644 index 00000000..1d145c8b --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/process.h @@ -0,0 +1,229 @@ +/*************************************************************************** + * Copyright (C) 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef PROCESS_H +#define PROCESS_H + +#include +#include +#include +#include +#include +#include "kernel.h" +#include "sync.h" +#include "elf_program.h" +#include "config/miosix_settings.h" +#include "filesystem/file_access.h" + +#ifdef WITH_PROCESSES + +namespace miosix { + +/** + * List of Miosix syscalls + */ +enum Syscalls +{ + // Yield. Can be called both by kernel threads and process threads both in + // userspace and kernelspace mode. It causes the scheduler to switch to + // another thread. It is the only SVC that is available also when processes + // are disabled in miosix_config.h. No parameters, no return value. + SYS_YIELD=0, + // Back to userspace. It is used by process threads running in kernelspace + // mode to return to userspace mode after completing an SVC. If called by a + // process thread already in userspace mode it does nothing. Use of this + // SVC by kernel threads is forbidden. No parameters, no return value. + SYS_USERSPACE=1, + + // Standard unix syscalls. Use of these SVC by kernel threads is forbidden. + SYS_EXIT=2, + SYS_WRITE=3, + SYS_READ=4, + SYS_USLEEP=5, + SYS_OPEN=6, + SYS_CLOSE=7, + SYS_LSEEK=8, + SYS_SYSTEM=9, + SYS_FSTAT=10, + SYS_ISATTY=11, + SYS_STAT=12, + SYS_LSTAT=13, + SYS_FCNTL=14, + SYS_IOCTL=15, + SYS_GETDENTS=16, + SYS_GETCWD=17, + SYS_CHDIR=18, + SYS_MKDIR=19, + SYS_RMDIR=20, + SYS_UNLINK=21, + SYS_RENAME=22 +}; + +//Forware decl +class Process; + +/** + * This class contains the fields that are in common between the kernel and + * processes + */ +class ProcessBase +{ +public: + /** + * Constructor + */ + ProcessBase() : pid(0), ppid(0) {} + + /** + * \return the process' pid + */ + pid_t getPid() const { return pid; } + + FileDescriptorTable& getFileTable() { return fileTable; } + +protected: + pid_t pid; /// childs; /// zombies; /// + */ + static pid_t getNewPid(); + + ElfProgram program; /// threads; /// * + ***************************************************************************/ + +#include "process_pool.h" +#include +#include + +using namespace std; + +#ifdef WITH_PROCESSES + +namespace miosix { + +ProcessPool& ProcessPool::instance() +{ + #ifndef TEST_ALLOC + //These are defined in the linker script + extern unsigned int _process_pool_start asm("_process_pool_start"); + extern unsigned int _process_pool_end asm("_process_pool_end"); + static ProcessPool pool(&_process_pool_start, + reinterpret_cast(&_process_pool_end)- + reinterpret_cast(&_process_pool_start)); + return pool; + #else //TEST_ALLOC + static ProcessPool pool(reinterpret_cast(0x20008000),96*1024); + return pool; + #endif //TEST_ALLOC +} + +unsigned int *ProcessPool::allocate(unsigned int size) +{ + #ifndef TEST_ALLOC + miosix::Lock l(mutex); + #endif //TEST_ALLOC + //If size is not a power of two, or too big or small + if((size & (size - 1)) || size>poolSize || size(poolBase) % size) + offset=size-(reinterpret_cast(poolBase) % size); + unsigned int startBit=offset/blockSize; + unsigned int sizeBit=size/blockSize; + + for(unsigned int i=startBit;i<=poolSize/blockSize;i+=sizeBit) + { + bool notEmpty=false; + for(unsigned int j=0;j l(mutex); + #endif //TEST_ALLOC + map::iterator it= allocatedBlocks.find(ptr); + if(it==allocatedBlocks.end())throw runtime_error(""); + unsigned int size =(it->second)/blockSize; + unsigned int firstBit=(reinterpret_cast(ptr)- + reinterpret_cast(poolBase))/blockSize; + for(unsigned int i=firstBit;i|d"<>op; + switch(op) + { + case 'a': + ss>>dec>>param; + try { + pool.allocate(1<>hex>>param; + try { + pool.deallocate(reinterpret_cast(param)); + } catch(exception& e) { + cout< * + ***************************************************************************/ + +#ifndef PROCESS_POOL +#define PROCESS_POOL + +#include + +#ifndef TEST_ALLOC +#include +#else //TEST_ALLOC +#include +#include +#include +#endif //TEST_ALLOC + +#ifdef WITH_PROCESSES + +namespace miosix { + +/** + * This class allows to handle a memory area reserved for the allocation of + * processes' images. This memory area is called process pool. + */ +class ProcessPool +{ +public: + /** + * \return an instance of the process pool (singleton) + */ + static ProcessPool& instance(); + + /** + * Allocate memory inside the process pool. + * \param size size of the requested memory, must be a power of two, + * and be grater or equal to blockSize. + * \return a pointer to the allocated memory. Note that the pointer is + * size-aligned, so that for example if a 16KByte block is requested, + * the returned pointer is aligned on a 16KB boundary. This is so to + * allow using the MPU of the Cortex-M3. + * \throws runtime_error in case the requested allocation is invalid, + * or bad_alloc if out of memory + */ + unsigned int *allocate(unsigned int size); + + /** + * Deallocate a memory block. + * \param ptr pointer to deallocate. + * \throws runtime_error if the pointer is invalid + */ + void deallocate(unsigned int *ptr); + + #ifdef TEST_ALLOC + /** + * Print the state of the allocator, used for debugging + */ + void printAllocatedBlocks() + { + using namespace std; + map::iterator it; + cout<second + << " allocated @ " << it->first< allocatedBlocks; + #ifndef TEST_ALLOC + miosix::FastMutex mutex; ///< Mutex to guard concurrent access + #endif //TEST_ALLOC +}; + +} //namespace miosix + +#endif //WITH_PROCESSES + +#endif //PROCESS_POOL diff --git a/lib/miosix-kernel/miosix/kernel/pthread.cpp b/lib/miosix-kernel/miosix/kernel/pthread.cpp new file mode 100644 index 00000000..c4f8ad3b --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/pthread.cpp @@ -0,0 +1,429 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +/* + * pthread.cpp Part of the Miosix Embedded OS. Provides a mapping of the + * posix thread API to the Miosix thread API. + */ + +#include +#include +#include +#include +#include +#include "kernel.h" +#include "error.h" +#include "pthread_private.h" + +using namespace miosix; + +// +// Newlib's pthread.h has been patched since Miosix 1.68 to contain a definition +// for pthread_mutex_t and pthread_cond_t that allows a fast implementation +// of mutexes and condition variables. This *requires* to use gcc 4.5.2 with +// Miosix specific patches. +// + +//These functions needs to be callable from C +extern "C" { + +// +// Thread related API +// + +int pthread_create(pthread_t *pthread, const pthread_attr_t *attr, + void *(*start)(void *), void *arg) +{ + Thread::Options opt=Thread::JOINABLE; + unsigned int stacksize=STACK_DEFAULT_FOR_PTHREAD; + unsigned int priority=1; + if(attr!=NULL) + { + if(attr->detachstate==PTHREAD_CREATE_DETACHED) + opt=Thread::DEFAULT; + stacksize=attr->stacksize; + // Cap priority value in the range between 0 and PRIORITY_MAX-1 + int prio=std::min(std::max(0, attr->schedparam.sched_priority), + PRIORITY_MAX-1); + // Swap unix-based priority back to the miosix one. + priority=(PRIORITY_MAX-1)-prio; + } + Thread *result=Thread::create(start,stacksize,priority,arg,opt); + if(result==0) return EAGAIN; + *pthread=reinterpret_cast(result); + return 0; +} + +int pthread_join(pthread_t pthread, void **value_ptr) +{ + Thread *t=reinterpret_cast(pthread); + if(Thread::exists(t)==false) return ESRCH; + if(t==Thread::getCurrentThread()) return EDEADLK; + if(t->join(value_ptr)==false) return EINVAL; + return 0; +} + +int pthread_detach(pthread_t pthread) +{ + Thread *t=reinterpret_cast(pthread); + if(Thread::exists(t)==false) return ESRCH; + t->detach(); + return 0; +} + +pthread_t pthread_self() +{ + return reinterpret_cast(Thread::getCurrentThread()); +} + +int pthread_equal(pthread_t t1, pthread_t t2) +{ + return t1==t2; +} + +int pthread_attr_init(pthread_attr_t *attr) +{ + //We only use three fields of pthread_attr_t so initialize only these + attr->detachstate=PTHREAD_CREATE_JOINABLE; + attr->stacksize=STACK_DEFAULT_FOR_PTHREAD; + //Default priority level is one above minimum. + attr->schedparam.sched_priority=PRIORITY_MAX-1-MAIN_PRIORITY; + return 0; +} + +int pthread_attr_destroy(pthread_attr_t *attr) +{ + (void) attr; + return 0; //That was easy +} + +int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate) +{ + *detachstate=attr->detachstate; + return 0; +} + +int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate) +{ + if(detachstate!=PTHREAD_CREATE_JOINABLE && + detachstate!=PTHREAD_CREATE_DETACHED) return EINVAL; + attr->detachstate=detachstate; + return 0; +} + +int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize) +{ + *stacksize=attr->stacksize; + return 0; +} + +int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize) +{ + if(stacksizestacksize=stacksize; + return 0; +} + +int pthread_attr_getschedparam (const pthread_attr_t *attr, + struct sched_param *param) +{ + *param = attr->schedparam; + return 0; +} + +int pthread_attr_setschedparam (pthread_attr_t *attr, + const struct sched_param *param) +{ + attr->schedparam = *param; + return 0; +} + +int sched_get_priority_max(int policy) +{ + (void) policy; + + // Unix-like thread priorities: max priority is zero. + return 0; +} + +int sched_get_priority_min(int policy) +{ + (void) policy; + + // Unix-like thread priorities: min priority is a value above zero. + // The value for PRIORITY_MAX is configured in miosix_settings.h + return PRIORITY_MAX - 1; +} + +int sched_yield() +{ + Thread::yield(); + return 0; +} + +// +// Mutex API +// + +int pthread_mutexattr_init(pthread_mutexattr_t *attr) +{ + attr->recursive=PTHREAD_MUTEX_DEFAULT; + return 0; +} + +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) +{ + (void) attr; + return 0; //Do nothing +} + +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *kind) +{ + *kind=attr->recursive; + return 0; +} + +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind) +{ + switch(kind) + { + case PTHREAD_MUTEX_DEFAULT: + attr->recursive=PTHREAD_MUTEX_DEFAULT; + return 0; + case PTHREAD_MUTEX_RECURSIVE: + attr->recursive=PTHREAD_MUTEX_RECURSIVE; + return 0; + default: + return EINVAL; + } +} + +int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) +{ + mutex->owner=0; + mutex->first=0; + //No need to initialize mutex->last + if(attr!=0) + { + mutex->recursive= attr->recursive==PTHREAD_MUTEX_RECURSIVE ? 0 : -1; + } else mutex->recursive=-1; + return 0; +} + +int pthread_mutex_destroy(pthread_mutex_t *mutex) +{ + if(mutex->owner!=0) return EBUSY; + return 0; +} + +int pthread_mutex_lock(pthread_mutex_t *mutex) +{ + FastInterruptDisableLock dLock; + IRQdoMutexLock(mutex,dLock); + return 0; +} + +int pthread_mutex_trylock(pthread_mutex_t *mutex) +{ + FastInterruptDisableLock dLock; + void *p=reinterpret_cast(Thread::IRQgetCurrentThread()); + if(mutex->owner==0) + { + mutex->owner=p; + return 0; + } + if(mutex->owner==p && mutex->recursive>=0) + { + mutex->recursive++; + return 0; + } + return EBUSY; +} + +int pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + #ifndef SCHED_TYPE_EDF + FastInterruptDisableLock dLock; + IRQdoMutexUnlock(mutex); + #else //SCHED_TYPE_EDF + bool hppw; + { + FastInterruptDisableLock dLock; + hppw=IRQdoMutexUnlock(mutex); + } + if(hppw) Thread::yield(); //If the woken thread has higher priority, yield + #endif //SCHED_TYPE_EDF + return 0; +} + +// +// Condition variable API +// + +int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) +{ + //attr is currently not considered + (void) attr; + cond->first=0; + //No need to initialize cond->last + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cond) +{ + if(cond->first!=0) return EBUSY; + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + FastInterruptDisableLock dLock; + Thread *p=Thread::IRQgetCurrentThread(); + WaitingList waiting; //Element of a linked list on stack + waiting.thread=reinterpret_cast(p); + waiting.next=0; //Putting this thread last on the list (lifo policy) + if(cond->first==0) + { + cond->first=&waiting; + cond->last=&waiting; + } else { + cond->last->next=&waiting; + cond->last=&waiting; + } + p->flags.IRQsetCondWait(true); + + unsigned int depth=IRQdoMutexUnlockAllDepthLevels(mutex); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); //Here the wait becomes effective + } + IRQdoMutexLockToDepth(mutex,dLock,depth); + return 0; +} + +int pthread_cond_signal(pthread_cond_t *cond) +{ + #ifdef SCHED_TYPE_EDF + bool hppw=false; + #endif //SCHED_TYPE_EDF + { + FastInterruptDisableLock dLock; + if(cond->first==0) return 0; + + Thread *t=reinterpret_cast(cond->first->thread); + t->flags.IRQsetCondWait(false); + cond->first=cond->first->next; + + #ifdef SCHED_TYPE_EDF + if(t->IRQgetPriority() >Thread::IRQgetCurrentThread()->IRQgetPriority()) + hppw=true; + #endif //SCHED_TYPE_EDF + } + #ifdef SCHED_TYPE_EDF + //If the woken thread has higher priority, yield + if(hppw) Thread::yield(); + #endif //SCHED_TYPE_EDF + return 0; +} + +int pthread_cond_broadcast(pthread_cond_t *cond) +{ + #ifdef SCHED_TYPE_EDF + bool hppw=false; + #endif //SCHED_TYPE_EDF + { + FastInterruptDisableLock lock; + while(cond->first!=0) + { + Thread *t=reinterpret_cast(cond->first->thread); + t->flags.IRQsetCondWait(false); + cond->first=cond->first->next; + + #ifdef SCHED_TYPE_EDF + if(t->IRQgetPriority() > + Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true; + #endif //SCHED_TYPE_EDF + } + } + #ifdef SCHED_TYPE_EDF + //If at least one of the woken thread has higher, yield + if(hppw) Thread::yield(); + #endif //SCHED_TYPE_EDF + return 0; +} + +// +// Once API +// + +int pthread_once(pthread_once_t *once, void (*func)()) +{ + if(once==nullptr || func==nullptr || once->is_initialized!=1) return EINVAL; + + bool again; + do { + { + FastInterruptDisableLock dLock; + switch(once->init_executed) + { + case 0: //We're the first ones (or previous call has thrown) + once->init_executed=1; + again=false; + break; + case 1: //Call started but not ended + again=true; + break; + default: //Already called, return immediately + return 0; + } + } + if(again) Thread::yield(); //Yield and let other thread complete + } while(again); + + #ifdef __NO_EXCEPTIONS + func(); + #else //__NO_EXCEPTIONS + try { + func(); + } catch(...) { + once->init_executed=0; //We failed, let some other thread try + throw; + } + #endif //__NO_EXCEPTIONS + once->init_executed=2; //We succeeded + return 0; +} + +int pthread_setcancelstate(int state, int *oldstate) +{ + //Stub + (void) state; + (void) oldstate; + return 0; +} + +} //extern "C" diff --git a/lib/miosix-kernel/miosix/kernel/pthread_private.h b/lib/miosix-kernel/miosix/kernel/pthread_private.h new file mode 100644 index 00000000..54c47156 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/pthread_private.h @@ -0,0 +1,221 @@ +/*************************************************************************** + * Copyright (C) 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +//This file contains private implementation details of mutexes, it's not +//meant to be used by end users + +#ifndef PTHREAD_PRIVATE_H +#define PTHREAD_PRIVATE_H + +namespace miosix { + +/** + * \internal + * Implementation code to lock a mutex. Must be called with interrupts disabled + * \param mutex mutex to be locked + * \param d The instance of FastInterruptDisableLock used to disable interrupts + */ +static inline void IRQdoMutexLock(pthread_mutex_t *mutex, + FastInterruptDisableLock& d) +{ + void *p=reinterpret_cast(Thread::IRQgetCurrentThread()); + if(mutex->owner==0) + { + mutex->owner=p; + return; + } + + //This check is very important. Without this attempting to lock the same + //mutex twice won't cause a deadlock because the Thread::IRQwait() is + //enclosed in a while(owner!=p) which is immeditely false. + if(mutex->owner==p) + { + if(mutex->recursive>=0) + { + mutex->recursive++; + return; + } else errorHandler(MUTEX_DEADLOCK); //Bad, deadlock + } + + WaitingList waiting; //Element of a linked list on stack + waiting.thread=p; + waiting.next=0; //Putting this thread last on the list (lifo policy) + if(mutex->first==0) + { + mutex->first=&waiting; + mutex->last=&waiting; + } else { + mutex->last->next=&waiting; + mutex->last=&waiting; + } + + //The while is necessary because some other thread might call wakeup() + //on this thread. So the thread can wakeup also for other reasons not + //related to the mutex becoming free + while(mutex->owner!=p) + { + Thread::IRQwait();//Returns immediately + { + FastInterruptEnableLock eLock(d); + Thread::yield(); //Now the IRQwait becomes effective + } + } +} + +/** + * \internal + * Implementation code to lock a mutex to a specified depth level. + * Must be called with interrupts disabled. If the mutex is not recursive the + * mutex is locked only one level deep regardless of the depth value. + * \param mutex mutex to be locked + * \param d The instance of FastInterruptDisableLock used to disable interrupts + * \param depth recursive depth at which the mutex will be locked. Zero + * means the mutex is locked one level deep (as if lock() was called once), + * one means two levels deep, etc. + */ +static inline void IRQdoMutexLockToDepth(pthread_mutex_t *mutex, + FastInterruptDisableLock& d, unsigned int depth) +{ + void *p=reinterpret_cast(Thread::IRQgetCurrentThread()); + if(mutex->owner==0) + { + mutex->owner=p; + if(mutex->recursive>=0) mutex->recursive=depth; + return; + } + + //This check is very important. Without this attempting to lock the same + //mutex twice won't cause a deadlock because the Thread::IRQwait() is + //enclosed in a while(owner!=p) which is immeditely false. + if(mutex->owner==p) + { + if(mutex->recursive>=0) + { + mutex->recursive=depth; + return; + } else errorHandler(MUTEX_DEADLOCK); //Bad, deadlock + } + + WaitingList waiting; //Element of a linked list on stack + waiting.thread=p; + waiting.next=0; //Putting this thread last on the list (lifo policy) + if(mutex->first==0) + { + mutex->first=&waiting; + mutex->last=&waiting; + } else { + mutex->last->next=&waiting; + mutex->last=&waiting; + } + + //The while is necessary because some other thread might call wakeup() + //on this thread. So the thread can wakeup also for other reasons not + //related to the mutex becoming free + while(mutex->owner!=p) + { + Thread::IRQwait();//Returns immediately + { + FastInterruptEnableLock eLock(d); + Thread::yield(); //Now the IRQwait becomes effective + } + } + if(mutex->recursive>=0) mutex->recursive=depth; +} + +/** + * \internal + * Implementation code to unlock a mutex. + * Must be called with interrupts disabled + * \param mutex mutex to unlock + * \return true if a higher priority thread was woken, + * only if EDF scheduler is selected, otherwise it always returns false + */ +static inline bool IRQdoMutexUnlock(pthread_mutex_t *mutex) +{ +// Safety check removed for speed reasons +// if(mutex->owner!=reinterpret_cast(Thread::IRQgetCurrentThread())) +// return false; + if(mutex->recursive>0) + { + mutex->recursive--; + return false; + } + if(mutex->first!=0) + { + Thread *t=reinterpret_cast(mutex->first->thread); + t->IRQwakeup(); + mutex->owner=mutex->first->thread; + mutex->first=mutex->first->next; + + #ifndef SCHED_TYPE_EDF + if(t->IRQgetPriority() >Thread::IRQgetCurrentThread()->IRQgetPriority()) + return true; + #endif //SCHED_TYPE_EDF + return false; + } + mutex->owner=0; + return false; +} + +/** + * \internal + * Implementation code to unlock all depth levels of a mutex. + * Must be called with interrupts disabled + * \param mutex mutex to unlock + * \return the mutex recursive depth (how many times it was locked by the + * owner). Zero means the mutex is locked one level deep (lock() was called + * once), one means two levels deep, etc. + */ +static inline unsigned int IRQdoMutexUnlockAllDepthLevels(pthread_mutex_t *mutex) +{ +// Safety check removed for speed reasons +// if(mutex->owner!=reinterpret_cast(Thread::IRQgetCurrentThread())) +// return false; + if(mutex->first!=0) + { + Thread *t=reinterpret_cast(mutex->first->thread); + t->IRQwakeup(); + mutex->owner=mutex->first->thread; + mutex->first=mutex->first->next; + + if(mutex->recursive<0) return 0; + unsigned int result=mutex->recursive; + mutex->recursive=0; + return result; + } + + mutex->owner=0; + + if(mutex->recursive<0) return 0; + unsigned int result=mutex->recursive; + mutex->recursive=0; + return result; +} + +} //namespace miosix + +#endif //PTHREAD_PRIVATE_H diff --git a/lib/miosix-kernel/miosix/kernel/queue.h b/lib/miosix-kernel/miosix/kernel/queue.h new file mode 100644 index 00000000..34dcf5fe --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/queue.h @@ -0,0 +1,554 @@ +/*************************************************************************** + * Copyright (C) 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef QUEUE_H +#define QUEUE_H + +#include "kernel.h" +#include "error.h" + +namespace miosix { + +/** + * \addtogroup Sync + * \{ + */ + +/** + * A queue, used to transfer data between TWO threads, or between ONE thread + * and an IRQ.
+ * If you need to tranfer data between more than two threads, you need to + * use mutexes to ensure that only one thread at a time calls get, and only one + * thread at a time calls put.
+ * Dynamically creating a queue with new or on the stack must be done with care, + * to avoid deleting a queue with a waiting thread, and to avoid situations + * where a thread tries to access a deleted queue. + * \tparam T the type of elements in the queue + * \tparam len the length of the Queue. Value 0 is forbidden + */ +template +class Queue +{ +public: + /** + * Constructor, create a new empty queue. + */ + Queue() : waiting(0), numElem(0), putPos(0), getPos(0) {} + + /** + * \return true if the queue is empty + */ + bool isEmpty() const { return numElem==0; } + + /** + * \return true if the queue is full + */ + bool isFull() const { return numElem==len; } + + /** + * \return the number of elements currently in the queue + */ + unsigned int size() const { return numElem; } + + /** + * \return the maximum number of elements the queue can hold + */ + unsigned int capacity() const { return len; } + + /** + * If a queue is empty, waits until the queue is not empty. + */ + void waitUntilNotEmpty(); + + /** + * If a queue is full, waits until the queue is not full + */ + void waitUntilNotFull(); + + /** + * Get an element from the queue. If the queue is empty, then sleep until + * an element becomes available. + * \param elem an element from the queue + */ + void get(T& elem); + + /** + * Put an element to the queue. If the queue is full, then sleep until a + * place becomes available. + * \param elem element to add to the queue + */ + void put(const T& elem); + + /** + * Get an element from the queue, only if the queue is not empty.
+ * Can ONLY be used inside an IRQ, or when interrupts are disabled. + * \param elem an element from the queue. The element is valid only if the + * return value is true + * \return true if the queue was not empty + */ + bool IRQget(T& elem); + + /** + * Get an element from the queue, only if the queue is not empty.
+ * Can ONLY be used inside an IRQ, or when interrupts are disabled. + * \param elem an element from the queue. The element is valid only if the + * return value is true + * \param hppw is not modified if no thread is woken or if the woken thread + * has a lower or equal priority than the currently running thread, else is + * set to true + * \return true if the queue was not empty + */ + bool IRQget(T& elem, bool& hppw); + + /** + * Put an element to the queue, only if th queue is not full.
+ * Can ONLY be used inside an IRQ, or when interrupts are disabled. + * \param elem element to add. The element has been added only if the + * return value is true + * \return true if the queue was not full. + */ + bool IRQput(const T& elem); + + /** + * Put an element to the queue, only if th queue is not full.
+ * Can ONLY be used inside an IRQ, or when interrupts are disabled. + * \param elem element to add. The element has been added only if the + * return value is true + * \param hppw is not modified if no thread is woken or if the woken thread + * has a lower or equal priority than the currently running thread, else is + * set to true + * \return true if the queue was not full. + */ + bool IRQput(const T& elem, bool& hppw); + + /** + * Clear all items in the queue.
+ * Cannot be used inside an IRQ + */ + void reset() + { + FastInterruptDisableLock lock; + IRQreset(); + } + + /** + * Same as reset(), but to be used only inside IRQs or when interrupts are + * disabled. + */ + void IRQreset() + { + IRQwakeWaitingThread(); + putPos=getPos=numElem=0; + } + +private: + //Unwanted methods + Queue(const Queue& s);///< No public copy constructor + Queue& operator = (const Queue& s);///< No publc operator = + + /** + * Wake an eventual waiting thread. + * Must be called when interrupts are disabled + */ + void IRQwakeWaitingThread() + { + if(!waiting) return; + waiting->IRQwakeup();//Wakeup eventual waiting thread + waiting=0; + } + + //Queue data + T buffer[len];///< queued elements are put here. Used as a ring buffer + Thread *waiting;///< If not null holds the thread waiting + volatile unsigned int numElem;///< nuber of elements in the queue + volatile unsigned int putPos; ///< index of buffer where to get next element + volatile unsigned int getPos; ///< index of buffer where to put next element +}; + +template +void Queue::waitUntilNotEmpty() +{ + FastInterruptDisableLock dLock; + IRQwakeWaitingThread(); + while(isEmpty()) + { + waiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + IRQwakeWaitingThread(); + } +} + +template +void Queue::waitUntilNotFull() +{ + FastInterruptDisableLock dLock; + IRQwakeWaitingThread(); + while(isFull()) + { + waiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + IRQwakeWaitingThread(); + } +} + +template +void Queue::get(T& elem) +{ + FastInterruptDisableLock dLock; + IRQwakeWaitingThread(); + while(isEmpty()) + { + waiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + IRQwakeWaitingThread(); + } + numElem--; + elem=buffer[getPos]; + if(++getPos==len) getPos=0; +} + +template +void Queue::put(const T& elem) +{ + FastInterruptDisableLock dLock; + IRQwakeWaitingThread(); + while(isFull()) + { + waiting=Thread::IRQgetCurrentThread(); + Thread::IRQwait(); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); + } + IRQwakeWaitingThread(); + } + numElem++; + buffer[putPos]=elem; + if(++putPos==len) putPos=0; +} + +template +bool Queue::IRQget(T& elem) +{ + IRQwakeWaitingThread(); + if(isEmpty()) return false; + numElem--; + elem=buffer[getPos]; + if(++getPos==len) getPos=0; + return true; +} + +template +bool Queue::IRQget(T& elem, bool& hppw) +{ + if(waiting && (waiting->IRQgetPriority() > + Thread::IRQgetCurrentThread()->IRQgetPriority())) hppw=true; + IRQwakeWaitingThread(); + if(isEmpty()) return false; + numElem--; + elem=buffer[getPos]; + if(++getPos==len) getPos=0; + return true; +} + +template +bool Queue::IRQput(const T& elem) +{ + IRQwakeWaitingThread(); + if(isFull()) return false; + numElem++; + buffer[putPos]=elem; + if(++putPos==len) putPos=0; + return true; +} + +template +bool Queue::IRQput(const T& elem, bool& hppw) +{ + if(waiting && (waiting->IRQgetPriority() > + Thread::IRQgetCurrentThread()->IRQgetPriority())) hppw=true; + IRQwakeWaitingThread(); + if(isFull()) return false; + numElem++; + buffer[putPos]=elem; + if(++putPos==len) putPos=0; + return true; +} + +//This partial specialization is meant to to produce compiler errors in case an +//attempt is made to instantiate a Queue with zero size, as it is forbidden +template class Queue {}; + +/** + * An unsynchronized circular buffer data structure with the storage dynamically + * allocated on the heap. + * Note that unlike Queue, this class is only a data structure and not a + * synchronization primitive. The synchronization between the thread and + * the IRQ (or the other thread) must be done by the caller. + */ +template +class DynUnsyncQueue +{ +public: + /** + * Constructor + * \param elem number of elements of the circular buffer + */ + DynUnsyncQueue(unsigned int elem) : data(new T[elem]), + putPos(0), getPos(0), queueSize(0), queueCapacity(elem) {} + + /** + * \return true if the queue is empty + */ + bool isEmpty() const { return queueSize==0; } + + /** + * \return true if the queue is full + */ + bool isFull() const { return queueSize==queueCapacity; } + + /** + * \return the number of elements currently in the queue + */ + unsigned int size() const { return queueSize; } + + /** + * \return the maximum number of elements the queue can hold + */ + unsigned int capacity() const { return queueCapacity; } + + /** + * Try to put an element in the circular buffer + * \param elem element to put + * \return true if the queue was not full + */ + bool tryPut(const T& elem); + + /** + * Try to get an element from the circular buffer + * \param elem element to get will be stored here + * \return true if the queue was not empty + */ + bool tryGet(T& elem); + + /** + * Erase all elements in the queue + */ + void reset() { putPos=getPos=queueSize=0; } + + /** + * Destructor + */ + ~DynUnsyncQueue() { delete[] data; } + +private: + DynUnsyncQueue(const DynUnsyncQueue&); + DynUnsyncQueue& operator=(const DynUnsyncQueue&); + + T *data; + unsigned int putPos,getPos; + volatile unsigned int queueSize; + const unsigned int queueCapacity; +}; + +template +bool DynUnsyncQueue::tryPut(const T& elem) +{ + if(isFull()) return false; + queueSize++; + data[putPos++]=elem; + if(putPos>=queueCapacity) putPos=0; + return true; +} + +template +bool DynUnsyncQueue::tryGet(T& elem) +{ + if(isEmpty()) return false; + queueSize--; + elem=data[getPos++]; + if(getPos>=queueCapacity) getPos=0; + return true; +} + +/** + * A class to handle double buffering, but also triple buffering and in general + * N-buffering. Works between two threads but is especially suited to + * synchronize between a thread and an interrupt routine.
+ * Note that unlike Queue, this class is only a data structure and not a + * synchronization primitive. The synchronization between the thread and + * the IRQ (or the other thread) must be done by the caller.
+ * The internal implementation treats the buffers as a circular queue of N + * elements, hence the name. + * \tparam T type of elements of the buffer, usually char or unsigned char + * \tparam size maximum size of a buffer + * \tparam numbuf number of buffers, the default is two resulting in a + * double buffering scheme. Values 0 and 1 are forbidden + */ +template +class BufferQueue +{ +public: + /** + * Constructor, all buffers are empty + */ + BufferQueue() : put(0), get(0), cnt(0) {} + + /** + * \return true if no buffer is available for reading + */ + bool isEmpty() const { return cnt==0; } + + /** + * \return true if no buffer is available for writing + */ + bool isFull() const { return cnt==numbuf; } + + /** + * \return the maximum size of a buffer + */ + unsigned int bufferMaxSize() const { return size; } + + /** + * \return the maximum number of buffers + */ + unsigned int numberOfBuffers() const { return numbuf; } + + /** + * This member function allows to retrieve a buffer ready to be written, + * if available. + * \param buffer the available buffer will be assigned here if available + * \return true if a writable buffer has been found, false otherwise. + * In this case the buffer parameter is not modified + */ + bool tryGetWritableBuffer(T *&buffer) + { + if(isFull()) return false; + buffer=buf[put]; + return true; + } + + /** + * After having called tryGetWritableBuffer() to retrieve a buffer and + * having filled it, this member function allows to mark the buffer as + * available on the reader side. + * \param actualSize actual size of buffer. It usually equals bufferMaxSize + * but can be a lower value in case there is less available data + */ + void bufferFilled(unsigned int actualSize) + { + if(isFull()) errorHandler(UNEXPECTED); + cnt++; + bufSize[put++]=actualSize; + if(put>=numbuf) put=0; + } + + /** + * \return the number of buffers available for writing (0 to numbuf) + */ + unsigned char availableForWriting() const { return numbuf-cnt; } + + /** + * This member function allows to retrieve a buffer ready to be read, + * if available. + * \param buffer the available buffer will be assigned here if available + * \param actualSize the actual size of the buffer, as reported by the + * writer side + * \return true if a readable buffer has been found, false otherwise. + * In this case the buffer and actualSize parameters are not modified + */ + bool tryGetReadableBuffer(const T *&buffer, unsigned int& actualSize) + { + if(isEmpty()) return false; + buffer=buf[get]; + actualSize=bufSize[get]; + return true; + } + + /** + * After having called tryGetReadableBuffer() to retrieve a buffer and + * having read it, this member function allows to mark the buffer as + * available on the writer side. + */ + void bufferEmptied() + { + if(isEmpty()) errorHandler(UNEXPECTED); + cnt--; + get++; + if(get>=numbuf) get=0; + } + + /** + * \return The number of buffers available for reading (0, to numbuf) + */ + unsigned char availableForReading() const { return cnt; } + + /** + * Reset the buffers. As a consequence, the queue becomes empty. + */ + void reset() + { + put=get=cnt=0; + } + +private: + //Unwanted methods + BufferQueue(const BufferQueue&); + BufferQueue& operator=(const BufferQueue&); + + T buf[numbuf][size]; // The buffers + unsigned int bufSize[numbuf]; //To handle partially empty buffers + unsigned char put; //Put pointer + unsigned char get; //Get pointer + volatile unsigned char cnt; //Number of filled buffers, either (0 to numbuf) +}; + +//These two partial specialization are meant to produce compiler errors in case +//an attempt is made to allocate a BufferQueue with zero or one buffer, as it +//is forbidden +template class BufferQueue {}; +template class BufferQueue {}; + +/** + * \} + */ + +} //namespace miosix + +#endif //QUEUE_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler.cpp b/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler.cpp new file mode 100644 index 00000000..09a1ea38 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler.cpp @@ -0,0 +1,315 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "control_scheduler.h" +#include "kernel/error.h" +#include "kernel/process.h" +#include + +using namespace std; + +#ifdef SCHED_TYPE_CONTROL_BASED + +namespace miosix { + +//These are defined in kernel.cpp +extern volatile Thread *cur; +extern volatile int kernel_running; + +// +// class ControlScheduler +// + +bool ControlScheduler::PKaddThread(Thread *thread, + ControlSchedulerPriority priority) +{ + #ifdef SCHED_CONTROL_FIXED_POINT + if(threadListSize>=64) return false; + #endif //SCHED_CONTROL_FIXED_POINT + thread->schedData.priority=priority; + { + //Note: can't use FastInterruptDisableLock here since this code is + //also called *before* the kernel is started. + //Using FastInterruptDisableLock would enable interrupts prematurely + //and cause all sorts of misterious crashes + InterruptDisableLock dLock; + thread->schedData.next=threadList; + threadList=thread; + threadListSize++; + SP_Tr+=bNominal; //One thread more, increase round time + IRQrecalculateAlfa(); + } + return true; +} + +bool ControlScheduler::PKexists(Thread *thread) +{ + if(thread==0) return false; + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + if(it==thread) + { + if(it->flags.isDeleted()) return false; //Found, but deleted + return true; + } + } + return false; +} + +void ControlScheduler::PKremoveDeadThreads() +{ + //Special case, threads at the head of the list + while(threadList!=0 && threadList->flags.isDeleted()) + { + Thread *toBeDeleted=threadList; + { + FastInterruptDisableLock dLock; + threadList=threadList->schedData.next; + threadListSize--; + SP_Tr-=bNominal; //One thread less, reduce round time + } + void *base=toBeDeleted->watermark; + toBeDeleted->~Thread(); + free(base); //Delete ALL thread memory + } + if(threadList!=0) + { + //General case, delete threads not at the head of the list + for(Thread *it=threadList;it->schedData.next!=0;it=it->schedData.next) + { + if(it->schedData.next->flags.isDeleted()==false) continue; + Thread *toBeDeleted=it->schedData.next; + { + FastInterruptDisableLock dLock; + it->schedData.next=it->schedData.next->schedData.next; + threadListSize--; + SP_Tr-=bNominal; //One thread less, reduce round time + } + void *base=toBeDeleted->watermark; + toBeDeleted->~Thread(); + free(base); //Delete ALL thread memory + } + } + { + FastInterruptDisableLock dLock; + IRQrecalculateAlfa(); + } +} + +void ControlScheduler::PKsetPriority(Thread *thread, + ControlSchedulerPriority newPriority) +{ + thread->schedData.priority=newPriority; + { + FastInterruptDisableLock dLock; + IRQrecalculateAlfa(); + } +} + +void ControlScheduler::IRQsetIdleThread(Thread *idleThread) +{ + idleThread->schedData.priority=-1; + idle=idleThread; + //Initializing curInRound to end() so that the first time + //IRQfindNextThread() is called the scheduling algorithm runs + if(threadListSize!=1) errorHandler(UNEXPECTED); + curInRound=0; +} + +Thread *ControlScheduler::IRQgetIdleThread() +{ + return idle; +} + +void ControlScheduler::IRQfindNextThread() +{ + // Warning: since this function is called within interrupt routines, it + //is not possible to add/remove elements to threadList, since that would + //require dynamic memory allocation/deallocation which is forbidden within + //interrupts. Iterating the list is safe, though + + if(kernel_running!=0) return;//If kernel is paused, do nothing + + if(cur!=idle) + { + //Not preempting from the idle thread, store actual burst time of + //the preempted thread + int Tp=miosix_private::AuxiliaryTimer::IRQgetValue(); + cur->schedData.Tp=Tp; + Tr+=Tp; + } + + //Find next thread to run + for(;;) + { + if(curInRound!=0) curInRound=curInRound->schedData.next; + if(curInRound==0) //Note: do not replace with an else + { + //Check these two statements: + //- If all threads are not ready, the scheduling algorithm must be + // paused and the idle thread is run instead + //- If the inner integral regulator of all ready threads saturated + // then the integral regulator of the outer regulator must stop + // increasing because the set point cannot be attained anyway. + bool allThreadNotReady=true; + bool allReadyThreadsSaturated=true; + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + if(it->flags.isReady()) + { + allThreadNotReady=false; + if(it->schedData.boctxsave; + #ifdef WITH_PROCESSES + MPUConfiguration::IRQdisable(); + #endif + miosix_private::AuxiliaryTimer::IRQsetValue(bIdle); + return; + } + + //End of round reached, run scheduling algorithm + curInRound=threadList; + IRQrunRegulator(allReadyThreadsSaturated); + } + + if(curInRound->flags.isReady()) + { + //Found a READY thread, so run this one + cur=curInRound; + #ifdef WITH_PROCESSES + if(const_cast(cur)->flags.isInUserspace()==false) + { + ctxsave=cur->ctxsave; + MPUConfiguration::IRQdisable(); + } else { + ctxsave=cur->userCtxsave; + //A kernel thread is never in userspace, so the cast is safe + static_cast(cur->proc)->mpu.IRQenable(); + } + #else //WITH_PROCESSES + ctxsave=cur->ctxsave; + #endif //WITH_PROCESSES + miosix_private::AuxiliaryTimer::IRQsetValue( + curInRound->schedData.bo/multFactor); + return; + } else { + //If we get here we have a non ready thread that cannot run, + //so regardless of the burst calculated by the scheduler + //we do not run it and set Tp to zero. + curInRound->schedData.Tp=0; + } + } +} + +void ControlScheduler::IRQrecalculateAlfa() +{ + //Sum of all priorities of all threads + //Note that since priority goes from 0 to PRIORITY_MAX-1 + //but priorities we need go from 1 to PRIORITY_MAX we need to add one + unsigned int sumPriority=0; + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + #ifdef ENABLE_FEEDFORWARD + //Count only ready threads + if(it->flags.isReady()) + sumPriority+=it->schedData.priority.get()+1;//Add one + #else //ENABLE_FEEDFORWARD + //Count all threads + sumPriority+=it->schedData.priority.get()+1;//Add one + #endif //ENABLE_FEEDFORWARD + } + //This can happen when ENABLE_FEEDFORWARD is set and no thread is ready + if(sumPriority==0) return; + #ifndef SCHED_CONTROL_FIXED_POINT + float base=1.0f/((float)sumPriority); + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + #ifdef ENABLE_FEEDFORWARD + //Assign zero bursts to blocked threads + if(it->flags.isReady()) + { + it->schedData.alfa=base*((float)(it->schedData.priority.get()+1)); + } else { + it->schedData.alfa=0; + } + #else //ENABLE_FEEDFORWARD + //Assign bursts irrespective of thread blocking status + it->schedData.alfa=base*((float)(it->schedData.priority.get()+1)); + #endif //ENABLE_FEEDFORWARD + } + #else //FIXED_POINT_MATH + //Sum of all alfa is maximum value for an unsigned short + unsigned int base=4096/sumPriority; + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + #ifdef ENABLE_FEEDFORWARD + //Assign zero bursts to blocked threads + if(it->flags.isReady()) + { + it->schedData.alfa=base*(it->schedData.priority.get()+1); + } else { + it->schedData.alfa=0; + } + #else //ENABLE_FEEDFORWARD + //Assign bursts irrespective of thread blocking status + it->schedData.alfa=base*(it->schedData.priority.get()+1); + #endif //ENABLE_FEEDFORWARD + } + #endif //FIXED_POINT_MATH + reinitRegulator=true; +} + +Thread *ControlScheduler::threadList=0; +unsigned int ControlScheduler::threadListSize=0; +Thread *ControlScheduler::curInRound=0; +Thread *ControlScheduler::idle=0; +int ControlScheduler::SP_Tr=0; +int ControlScheduler::Tr=bNominal; +int ControlScheduler::bco=0; +int ControlScheduler::eTro=0; +bool ControlScheduler::reinitRegulator=false; + +} //namespace miosix + +#endif //SCHED_TYPE_CONTROL_BASED diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler.h b/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler.h new file mode 100644 index 00000000..a52db3bd --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler.h @@ -0,0 +1,282 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef CONTROL_SCHEDULER_H +#define CONTROL_SCHEDULER_H + +#include "config/miosix_settings.h" +#include "control_scheduler_types.h" +#include "parameters.h" +#include "kernel/kernel.h" +#include + +#ifdef SCHED_TYPE_CONTROL_BASED + +namespace miosix { + +/** + * \internal + * Control based scheduler. + */ +class ControlScheduler +{ +public: + /** + * \internal + * Add a new thread to the scheduler. + * This is called when a thread is created. + * \param thread a pointer to a valid thread instance. + * The behaviour is undefined if a thread is added multiple timed to the + * scheduler, or if thread is NULL. + * \param priority the priority of the new thread. + * Priority must be a positive value. + * Note that the meaning of priority is scheduler specific. + */ + static bool PKaddThread(Thread *thread, ControlSchedulerPriority priority); + + /** + * \internal + * \return true if thread exists, false if does not exist or has been + * deleted. A joinable thread is considered existing until it has been + * joined, even if it returns from its entry point (unless it is detached + * and terminates). + * + * Can be called both with the kernel paused and with interrupts disabled. + */ + static bool PKexists(Thread *thread); + + /** + * \internal + * Called when there is at least one dead thread to be removed from the + * scheduler + */ + static void PKremoveDeadThreads(); + + /** + * \internal + * Set the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be changed. + * \param newPriority new thread priority. + * Priority must be a positive value. + */ + static void PKsetPriority(Thread *thread, + ControlSchedulerPriority newPriority); + + /** + * \internal + * Get the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static ControlSchedulerPriority getPriority(Thread *thread) + { + return thread->schedData.priority; + } + + /** + * \internal + * Same as getPriority, but meant to be called with interrupts disabled. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static ControlSchedulerPriority IRQgetPriority(Thread *thread) + { + return thread->schedData.priority; + } + + /** + * \internal + * This is called before the kernel is started to by the kernel. The given + * thread is the idle thread, to be run all the times where no other thread + * can run. + */ + static void IRQsetIdleThread(Thread *idleThread); + + /** + * \internal + * \return the idle thread. + */ + static Thread *IRQgetIdleThread(); + + /** + * \internal + * This member function is called by the kernel every time a thread changes + * its running status. For example when a thread become sleeping, waiting, + * deleted or if it exits the sleeping or waiting status + */ + static void IRQwaitStatusHook() + { + #ifdef ENABLE_FEEDFORWARD + IRQrecalculateAlfa(); + #endif //ENABLE_FEEDFORWARD + } + + /** + * \internal + * This function is used to develop interrupt driven peripheral drivers.
+ * Can be used ONLY inside an IRQ (and not when interrupts are disabled) to + * find next thread in READY status. If the kernel is paused, does nothing. + * Can be used for example if an IRQ causes a higher priority thread to be + * woken, to change context. Note that to use this function the IRQ must + * use the macros to save/restore context defined in portability.h + * + * If the kernel is paused does nothing. + * It's behaviour is to modify the global variable miosix::cur which always + * points to the currently running thread. + */ + static void IRQfindNextThread(); + +private: + + /** + * \internal + * When priorities are modified, this function recalculates alfa for each + * thread. Must be called with kernel paused + */ + static void IRQrecalculateAlfa(); + + /** + * Called by IRQfindNextThread(), this function is where the control based + * scheduling algorithm is run. It is called once per round. + */ + static void IRQrunRegulator(bool allReadyThreadsSaturated) + { + using namespace std; + #ifdef SCHED_CONTROL_FIXED_POINT + //The fixed point scheduler may overflow if Tr is higher than this + Tr=min(Tr,524287); + #endif //FIXED_POINT_MATH + #ifdef ENABLE_REGULATOR_REINIT + if(reinitRegulator==false) + { + #endif //ENABLE_REGULATOR_REINIT + int eTr=SP_Tr-Tr; + #ifndef SCHED_CONTROL_FIXED_POINT + int bc=bco+static_cast(krr*eTr-krr*zrr*eTro); + #else //FIXED_POINT_MATH + //Tr is clamped to 524287, so eTr uses at most 19bits. Considering + //the 31bits of a signed int, we have 12bits free. + const int fixedKrr=static_cast(krr*2048); + const int fixedKrrZrr=static_cast(krr*zrr*1024); + int bc=bco+(fixedKrr*eTr)/2048-(fixedKrrZrr*eTro)/1024; + #endif //FIXED_POINT_MATH + if(allReadyThreadsSaturated) + { + //If all inner regulators reached upper saturation, + //allow only a decrease in the burst correction. + if(bc(max(bco,-Tr),bMax*threadListSize); + #ifndef SCHED_CONTROL_FIXED_POINT + float nextRoundTime=static_cast(Tr+bco); + #else //FIXED_POINT_MATH + unsigned int nextRoundTime=Tr+bco; //Bounded to 20bits + #endif //FIXED_POINT_MATH + eTro=eTr; + Tr=0;//Reset round time + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + //Recalculate per thread set point + #ifndef SCHED_CONTROL_FIXED_POINT + it->schedData.SP_Tp=static_cast( + it->schedData.alfa*nextRoundTime); + #else //FIXED_POINT_MATH + //nextRoundTime is bounded to 20bits, alfa to 12bits, + //so the multiplication fits in 32bits + it->schedData.SP_Tp=(it->schedData.alfa*nextRoundTime)/4096; + #endif //FIXED_POINT_MATH + + //Run each thread internal regulator + int eTp=it->schedData.SP_Tp - it->schedData.Tp; + //note: since b and bo contain the real value multiplied by + //multFactor, this equals b=bo+eTp/multFactor. + int b=it->schedData.bo + eTp; + //saturation + it->schedData.bo=min(max(b,bMin*multFactor),bMax*multFactor); + } + #ifdef ENABLE_REGULATOR_REINIT + } else { + reinitRegulator=false; + Tr=0;//Reset round time + //Reset state of the external regulator + eTro=0; + bco=0; + + for(Thread *it=threadList;it!=0;it=it->schedData.next) + { + //Recalculate per thread set point + #ifndef SCHED_CONTROL_FIXED_POINT + it->schedData.SP_Tp=static_cast(it->schedData.alfa*SP_Tr); + #else //FIXED_POINT_MATH + //SP_Tr is bounded to 20bits, alfa to 12bits, + //so the multiplication fits in 32bits + it->schedData.SP_Tp=(it->schedData.alfa*SP_Tr)/4096; + #endif //FIXED_POINT_MATH + + int b=it->schedData.SP_Tp*multFactor; + it->schedData.bo=min(max(b,bMin*multFactor),bMax*multFactor); + } + } + #endif //ENABLE_REGULATOR_REINIT + } + + ///\internal Threads (except idle thread) are stored here + static Thread *threadList; + static unsigned int threadListSize; + + ///\internal current thread in the round + static Thread *curInRound; + + ///\internal idle thread + static Thread *idle; + + ///\internal Set point of round time + ///Current policy = bNominal * actual # of threads + static int SP_Tr; + + ///\internal Round time. + static int Tr; + + ///\internal old burst correction + static int bco; + + ///\internal old round tome error + static int eTro; + + ///\internal set to true by IRQrecalculateAlfa() to signal that + ///due to a change in alfa the regulator needs to be reinitialized + static bool reinitRegulator; +}; + +} //namespace miosix + +#endif //SCHED_TYPE_CONTROL_BASED + +#endif //CONTROL_SCHEDULER_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler_types.h b/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler_types.h new file mode 100644 index 00000000..c21a34dd --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/control/control_scheduler_types.h @@ -0,0 +1,140 @@ +/*************************************************************************** + * Copyright (C) 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "config/miosix_settings.h" +#include "parameters.h" + +#ifndef CONTROL_SCHEDULER_TYPES_H +#define CONTROL_SCHEDULER_TYPES_H + +#ifdef SCHED_TYPE_CONTROL_BASED + +namespace miosix { + +class Thread; //Forward declaration + +/** + * This class models the concept of priority for the control based scheduler. + * In this scheduler the priority is simply a short int with values ranging + * from 0 to PRIORITY_MAX-1, higher values mean higher priority, and the special + * value -1 reserved for the idle thread. + * Higher values of priority mean that the scheduler assigns a larger fraction + * of the round time to the thread. + */ +class ControlSchedulerPriority +{ +public: + /** + * Constructor. Not explicit for backward compatibility. + * \param priority the desired priority value. + */ + ControlSchedulerPriority(short int priority): priority(priority) {} + + /** + * Default constructor. + */ + ControlSchedulerPriority(): priority(MAIN_PRIORITY) {} + + /** + * \return the priority value + */ + short int get() const { return priority; } + + /** + * \return true if this objects represents a valid priority. + * Note that the value -1 is considered not valid, because it is reserved + * for the idle thread. + */ + bool validate() const + { + return this->priority>=0 && this->priority(ControlSchedulerPriority a, ControlSchedulerPriority b) +{ + return a.get() > b.get(); +} + +inline bool operator >=(ControlSchedulerPriority a, ControlSchedulerPriority b) +{ + return a.get() >= b.get(); +} + +inline bool operator ==(ControlSchedulerPriority a, ControlSchedulerPriority b) +{ + return a.get() == b.get(); +} + +inline bool operator !=(ControlSchedulerPriority a, ControlSchedulerPriority b) +{ + return a.get() != b.get(); +} + +/** + * \internal + * An instance of this class is embedded in every Thread class. It contains all + * the per-thread data required by the scheduler. + */ +class ControlSchedulerData +{ +public: + ControlSchedulerData(): priority(0), bo(bNominal*multFactor), alfa(0), + SP_Tp(0), Tp(bNominal), next(0) {} + + //Thread priority. Higher priority means longer burst + ControlSchedulerPriority priority; + int bo;//Old burst time, is kept here multiplied by multFactor + #ifndef SCHED_CONTROL_FIXED_POINT + float alfa; //Sum of all alfa=1 + #else //FIXED_POINT_MATH + //Sum of all alfa is 4096 except for some rounding error + unsigned short alfa; + #endif //FIXED_POINT_MATH + int SP_Tp;//Processing time set point + int Tp;//Real processing time + Thread *next;//Next thread in list +}; + +} //namespace miosix + +#endif //SCHED_TYPE_CONTROL_BASED + +#endif //CONTROL_SCHEDULER_TYPES_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/control/parameters.h b/lib/miosix-kernel/miosix/kernel/scheduler/control/parameters.h new file mode 100644 index 00000000..c6586139 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/control/parameters.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef PARAMETERS_H +#define PARAMETERS_H + +namespace miosix { + +// +// Parameters for the control based scheduler +// + +///Enabe feedforward in the control scheduler. Feedforward means that every +///thread blocking (due to a sleep, wait, or termination) cause the update of +///the set point (alfa). If it is disabled the scheduler reacts to blocking +///using feedback only in the external PI regulator. +#define ENABLE_FEEDFORWARD + +///Enable regulator reinitialization. This works in combination with +///ENABLE_FEEDFORWARD to avoid spikes when thread blocking/unblocking occurs. +///Essentially, every time the regulator set point changes, the state of the +///integral regulators is reset to its default value. +#define ENABLE_REGULATOR_REINIT + +///Run the scheduler using fixed point math only. Faster but less precise. +///Note that the inner integral regulators are always fixed point, this affects +///round partitioning and the external PI regulator. +///Also note this imposes a number of limits: +///- the number of threads has a maximum of 64 (this constrain is enforced in +/// PKaddThread() +///- the max "priority" is limited to 63 (this constraint is enforced by +/// priority valdation, as usual) +///- both krr and zrr must be less than 1.99f (this constraint is not enforced, +/// if a wrong value is set strange things may happen) +///- the maximum average burst must be less than 8192. Individual bursts may +/// exceed this, but the su of all bursts in the Tr variable can't exceed +/// 64 (max # threads) * 8191 = ~524287 (this constraint is enforced by +/// clamping Tr to that value) +//#define SCHED_CONTROL_FIXED_POINT + +#if defined(ENABLE_REGULATOR_REINIT) && !defined(ENABLE_FEEDFORWARD) +#error "ENABLE_REGULATOR_REINIT requires ENABLE_FEEDFORWARD" +#endif + +const float kpi=0.5; +const float krr=0.9;//1.4f; +const float zrr=0.88f; + +///Implementation detail resulting from a fixed point implementation of the +///inner integral regulators. Never change this, change kpi instead. +const int multFactor=static_cast(1.0f/kpi); + +///Instead of fixing a round time the current policy is to have +///roundTime=bNominal * numThreads, where bNominal is the nominal thread burst +static const int bNominal=static_cast(AUX_TIMER_CLOCK*0.004);// 4ms + +///minimum burst time (to avoid inefficiency caused by context switch +///time longer than burst time) +static const int bMin=static_cast(AUX_TIMER_CLOCK*0.0002);// 200us + +///maximum burst time (to avoid losing responsiveness/timer overflow) +static const int bMax=static_cast(AUX_TIMER_CLOCK*0.02);// 20ms + +///idle thread has a fixed burst length that can be modified here +///it is recomended to leave it to a high value, but that does not cause +///overflow of the auxiliary timer +static const int bIdle=static_cast(AUX_TIMER_CLOCK*0.5);// 500ms + +} + +#endif //PARAMETERS_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler.cpp b/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler.cpp new file mode 100644 index 00000000..38670848 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler.cpp @@ -0,0 +1,191 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "edf_scheduler.h" +#include "kernel/error.h" +#include "kernel/process.h" +#include + +using namespace std; + +#ifdef SCHED_TYPE_EDF + +namespace miosix { + +//These are defined in kernel.cpp +extern volatile Thread *cur; +extern volatile int kernel_running; + +// +// class EDFScheduler +// + +bool EDFScheduler::PKaddThread(Thread *thread, EDFSchedulerPriority priority) +{ + thread->schedData.deadline=priority; + add(thread); + return true; +} + +bool EDFScheduler::PKexists(Thread *thread) +{ + Thread *walk=head; + while(walk!=0) + { + if(walk==thread && (! (walk->flags.isDeleted()))) return true; + walk=walk->schedData.next; + } + return false; +} + +void EDFScheduler::PKremoveDeadThreads() +{ + //Delete all threads at the beginning of the list + for(;;) + { + if(head==0) errorHandler(UNEXPECTED); //Empty list is wrong. + if(head->flags.isDeleted()==false) break; + Thread *toBeDeleted=head; + head=head->schedData.next; + void *base=toBeDeleted->watermark; + toBeDeleted->~Thread(); + free(base); //Delete ALL thread memory + } + //When we get here this->head is not null and does not need to be deleted + Thread *walk=head; + for(;;) + { + if(walk->schedData.next==0) break; + if(walk->schedData.next->flags.isDeleted()) + { + Thread *toBeDeleted=walk->schedData.next; + walk->schedData.next=walk->schedData.next->schedData.next; + void *base=toBeDeleted->watermark; + toBeDeleted->~Thread(); + free(base); //Delete ALL thread memory + } else walk=walk->schedData.next; + } +} + +void EDFScheduler::PKsetPriority(Thread *thread, + EDFSchedulerPriority newPriority) +{ + remove(thread); + thread->schedData.deadline=newPriority; + add(thread); +} + +void EDFScheduler::IRQsetIdleThread(Thread *idleThread) +{ + idleThread->schedData.deadline=numeric_limits::max()-1; + add(idleThread); +} + +void EDFScheduler::IRQfindNextThread() +{ + if(kernel_running!=0) return;//If kernel is paused, do nothing + + Thread *walk=head; + for(;;) + { + if(walk==0) errorHandler(UNEXPECTED); + if(walk->flags.isReady()) + { + cur=walk; + #ifdef WITH_PROCESSES + if(const_cast(cur)->flags.isInUserspace()==false) + { + ctxsave=cur->ctxsave; + MPUConfiguration::IRQdisable(); + } else { + ctxsave=cur->userCtxsave; + //A kernel thread is never in userspace, so the cast is safe + static_cast(cur->proc)->mpu.IRQenable(); + } + #else //WITH_PROCESSES + ctxsave=cur->ctxsave; + #endif //WITH_PROCESSES + return; + } + walk=walk->schedData.next; + } +} + +void EDFScheduler::add(Thread *thread) +{ + long long newDeadline=thread->schedData.deadline.get(); + if(head==0) + { + head=thread; + return; + } + if(newDeadline<=head->schedData.deadline.get()) + { + thread->schedData.next=head; + head=thread; + return; + } + Thread *walk=head; + for(;;) + { + if(walk->schedData.next==0 || newDeadline<= + walk->schedData.next->schedData.deadline.get()) + { + thread->schedData.next=walk->schedData.next; + walk->schedData.next=thread; + break; + } + walk=walk->schedData.next; + } +} + +void EDFScheduler::remove(Thread *thread) +{ + if(head==0) errorHandler(UNEXPECTED); + if(head==thread) + { + head=head->schedData.next; + return; + } + Thread *walk=head; + for(;;) + { + if(walk->schedData.next==0) errorHandler(UNEXPECTED); + if(walk->schedData.next==thread) + { + walk->schedData.next=walk->schedData.next->schedData.next; + break; + } + walk=walk->schedData.next; + } +} + +Thread *EDFScheduler::head=0; + +} //namespace miosix + +#endif //SCHED_TYPE_EDF diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler.h b/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler.h new file mode 100644 index 00000000..e4921bcb --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler.h @@ -0,0 +1,160 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef EDF_SCHEDULER_H +#define EDF_SCHEDULER_H + +#include "config/miosix_settings.h" +#include "edf_scheduler_types.h" +#include "kernel/kernel.h" +#include + +#ifdef SCHED_TYPE_EDF + +namespace miosix { + +/** + * \internal + * EDF based scheduler. + */ +class EDFScheduler +{ +public: + /** + * \internal + * Add a new thread to the scheduler. + * This is called when a thread is created. + * \param thread a pointer to a valid thread instance. + * The behaviour is undefined if a thread is added multiple timed to the + * scheduler, or if thread is NULL. + * \param priority the priority of the new thread. + * Priority must be a positive value. + * Note that the meaning of priority is scheduler specific. + */ + static bool PKaddThread(Thread *thread, EDFSchedulerPriority priority); + + /** + * \return true if thread exists, false if does not exist or has been + * deleted. A joinable thread is considered existing until it has been + * joined, even if it returns from its entry point (unless it is detached + * and terminates). + * + * Can be called both with the kernel paused and with interrupts disabled. + */ + static bool PKexists(Thread *thread); + + /** + * \internal + * Called when there is at least one dead thread to be removed from the + * scheduler + */ + static void PKremoveDeadThreads(); + + /** + * \internal + * Set the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be changed. + * \param newPriority new thread priority. + * Priority must be a positive value. + */ + static void PKsetPriority(Thread *thread, EDFSchedulerPriority newPriority); + + /** + * \internal + * Get the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static EDFSchedulerPriority getPriority(Thread *thread) + { + return thread->schedData.deadline; + } + + /** + * Same as getPriority, but meant to be called with interrupts disabled. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static EDFSchedulerPriority IRQgetPriority(Thread *thread) + { + return thread->schedData.deadline; + } + + /** + * \internal + * This is called before the kernel is started to by the kernel. The given + * thread is the idle thread, to be run all the times where no other thread + * can run. + */ + static void IRQsetIdleThread(Thread *idleThread); + + /** + * \internal + * This member function is called by the kernel every time a thread changes + * its running status. For example when a thread become sleeping, waiting, + * deleted or if it exits the sleeping or waiting status + */ + static void IRQwaitStatusHook() {} + + /** + * This function is used to develop interrupt driven peripheral drivers.
+ * Can be used ONLY inside an IRQ (and not when interrupts are disabled) to + * find next thread in READY status. If the kernel is paused, does nothing. + * Can be used for example if an IRQ causes a higher priority thread to be + * woken, to change context. Note that to use this function the IRQ must + * use the macros to save/restore context defined in portability.h + * + * If the kernel is paused does nothing. + * It's behaviour is to modify the global variable miosix::cur which always + * points to the currently running thread. + */ + static void IRQfindNextThread(); + +private: + + /** + * Add a thread to the list of threads, keeping the list ordered by deadline + * \param thread thread to add + */ + static void add(Thread *thread); + + /** + * Remove a thread to the list of threads. + * \param thread thread to remove + */ + static void remove(Thread *thread); + + static Thread *head;///<\internal Head of threads list, ordered by deadline +}; + +} //namespace miosix + +#endif //SCHED_TYPE_EDF + +#endif //EDF_SCHEDULER_H \ No newline at end of file diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler_types.h b/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler_types.h new file mode 100644 index 00000000..a2e4fc3a --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/edf/edf_scheduler_types.h @@ -0,0 +1,140 @@ +/*************************************************************************** + * Copyright (C) 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "config/miosix_settings.h" +#include + +#ifndef EDF_SCHEDULER_TYPES_H +#define EDF_SCHEDULER_TYPES_H + +#ifdef SCHED_TYPE_EDF + +namespace miosix { + +class Thread; //Forward declaration + +/** + * This class models the concept of priority for the EDF scheduler. + * Therefore, it represents a deadline, which is the absolute time withi which + * the thread should have completed its computation. + */ +class EDFSchedulerPriority +{ +public: + /** + * Constructor. Not explicit for backward compatibility. + * \param deadline the thread deadline. + */ + EDFSchedulerPriority(long long deadline): deadline(deadline) {} + + /** + * Default constructor. + */ + EDFSchedulerPriority(): deadline(MAIN_PRIORITY) {} + + /** + * \return the priority value + */ + long long get() const { return deadline; } + + /** + * \return true if this objects represents a valid deadline. + */ + bool validate() const + { + // Deadlines must be positive, ant this is easy to understand. + // The reason why numeric_limits::max()-1 is not allowed, is + // because it is reserved for the idle thread. + // Note that numeric_limits::max() is instead allowed, and + // is used for thread that have no deadline assigned. In this way their + // deadline comes after the deadline of the idle thread, and they never + // run. + return this->deadline>=0 && + this->deadline!=std::numeric_limits::max()-1; + } + +private: + long long deadline;///< The deadline time +}; + +inline bool operator <(EDFSchedulerPriority a, EDFSchedulerPriority b) +{ + //Not that the comparison is reversed on purpose. In fact, the thread which + //has the lower deadline has higher priority + return a.get() > b.get(); +} + +inline bool operator <=(EDFSchedulerPriority a, EDFSchedulerPriority b) +{ + //Not that the comparison is reversed on purpose. In fact, the thread which + //has the lower deadline has higher priority + return a.get() >= b.get(); +} + +inline bool operator >(EDFSchedulerPriority a, EDFSchedulerPriority b) +{ + //Not that the comparison is reversed on purpose. In fact, the thread which + //has the lower deadline has higher priority + return a.get() < b.get(); +} + +inline bool operator >=(EDFSchedulerPriority a, EDFSchedulerPriority b) +{ + //Not that the comparison is reversed on purpose. In fact, the thread which + //has the lower deadline has higher priority + return a.get() <= b.get(); +} + +inline bool operator ==(EDFSchedulerPriority a, EDFSchedulerPriority b) +{ + return a.get() == b.get(); +} + +inline bool operator !=(EDFSchedulerPriority a, EDFSchedulerPriority b) +{ + return a.get() != b.get(); +} + +/** + * \internal + * An instance of this class is embedded in every Thread class. It contains all + * the per-thread data required by the scheduler. + */ +class EDFSchedulerData +{ +public: + EDFSchedulerData(): deadline(), next(0) {} + + EDFSchedulerPriority deadline; ///<\internal thread deadline + Thread *next; ///<\internal to make a list of threads, ordered by deadline +}; + +} //namespace miosix + +#endif //SCHED_TYPE_EDF + +#endif //EDF_SCHEDULER_TYPES_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.cpp b/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.cpp new file mode 100644 index 00000000..6f58904e --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.cpp @@ -0,0 +1,247 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "priority_scheduler.h" +#include "kernel/error.h" +#include "kernel/process.h" + +#ifdef SCHED_TYPE_PRIORITY + +namespace miosix { + +//These are defined in kernel.cpp +extern volatile Thread *cur; +extern volatile int kernel_running; + +// +// class PriorityScheduler +// + +bool PriorityScheduler::PKaddThread(Thread *thread, + PrioritySchedulerPriority priority) +{ + thread->schedData.priority=priority; + if(thread_list[priority.get()]==NULL) + { + thread_list[priority.get()]=thread; + thread->schedData.next=thread;//Circular list + } else { + thread->schedData.next=thread_list[priority.get()]->schedData.next; + thread_list[priority.get()]->schedData.next=thread; + } + return true; +} + +bool PriorityScheduler::PKexists(Thread *thread) +{ + for(int i=PRIORITY_MAX-1;i>=0;i--) + { + if(thread_list[i]==NULL) continue; + Thread *temp=thread_list[i]; + for(;;) + { + if((temp==thread)&&(! (temp->flags.isDeleted()))) + { + //Found + return true; + } + temp=temp->schedData.next; + if(temp==thread_list[i]) break; + } + } + return false; +} + +void PriorityScheduler::PKremoveDeadThreads() +{ + for(int i=PRIORITY_MAX-1;i>=0;i--) + { + if(thread_list[i]==NULL) continue; + bool first=false;//If false the tail of the list hasn't been calculated + Thread *tail=NULL;//Tail of the list + //Special case: removing first element in the list + while(thread_list[i]->flags.isDeleted()) + { + if(thread_list[i]->schedData.next==thread_list[i]) + { + //Only one element in the list + //Call destructor manually because of placement new + void *base=thread_list[i]->watermark; + thread_list[i]->~Thread(); + free(base); //Delete ALL thread memory + thread_list[i]=NULL; + break; + } + //If it is the first time the tail of the list hasn't + //been calculated + if(first==false) + { + first=true; + tail=thread_list[i]; + while(tail->schedData.next!=thread_list[i]) + tail=tail->schedData.next; + } + Thread *d=thread_list[i];//Save a pointer to the thread + thread_list[i]=thread_list[i]->schedData.next;//Remove from list + //Fix the tail of the circular list + tail->schedData.next=thread_list[i]; + //Call destructor manually because of placement new + void *base=d->watermark; + d->~Thread(); + free(base);//Delete ALL thread memory + } + if(thread_list[i]==NULL) continue; + //If it comes here, the first item is not NULL, and doesn't have + //to be deleted General case: removing items not at the first + //place + Thread *temp=thread_list[i]; + for(;;) + { + if(temp->schedData.next==thread_list[i]) break; + if(temp->schedData.next->flags.isDeleted()) + { + Thread *d=temp->schedData.next;//Save a pointer to the thread + //Remove from list + temp->schedData.next=temp->schedData.next->schedData.next; + //Call destructor manually because of placement new + void *base=d->watermark; + d->~Thread(); + free(base);//Delete ALL thread memory + } else temp=temp->schedData.next; + } + } +} + +void PriorityScheduler::PKsetPriority(Thread *thread, + PrioritySchedulerPriority newPriority) +{ + PrioritySchedulerPriority oldPriority=getPriority(thread); + //First set priority to the new value + thread->schedData.priority=newPriority; + //Then remove the thread from its old list + if(thread_list[oldPriority.get()]==thread) + { + if(thread_list[oldPriority.get()]->schedData.next== + thread_list[oldPriority.get()]) + { + //Only one element in the list + thread_list[oldPriority.get()]=NULL; + } else { + Thread *tail=thread_list[oldPriority.get()];//Tail of the list + while(tail->schedData.next!=thread_list[oldPriority.get()]) + tail=tail->schedData.next; + //Remove + thread_list[oldPriority.get()]= + thread_list[oldPriority.get()]->schedData.next; + //Fix tail of the circular list + tail->schedData.next=thread_list[oldPriority.get()]; + } + } else { + //If it comes here, the first item doesn't have to be removed + //General case: removing item not at the first place + Thread *temp=thread_list[oldPriority.get()]; + for(;;) + { + if(temp->schedData.next==thread_list[oldPriority.get()]) + { + //After walking all elements in the list the thread wasn't found + //This should never happen + errorHandler(UNEXPECTED); + } + if(temp->schedData.next==thread) + { + //Remove from list + temp->schedData.next=temp->schedData.next->schedData.next; + break; + } else temp=temp->schedData.next; + } + } + //Last insert the thread in the new list + if(thread_list[newPriority.get()]==NULL) + { + thread_list[newPriority.get()]=thread; + thread->schedData.next=thread;//Circular list + } else { + thread->schedData.next=thread_list[newPriority.get()]->schedData.next; + thread_list[newPriority.get()]->schedData.next=thread; + } +} + +void PriorityScheduler::IRQsetIdleThread(Thread *idleThread) +{ + idleThread->schedData.priority=-1; + idle=idleThread; +} + +void PriorityScheduler::IRQfindNextThread() +{ + if(kernel_running!=0) return;//If kernel is paused, do nothing + for(int i=PRIORITY_MAX-1;i>=0;i--) + { + if(thread_list[i]==NULL) continue; + Thread *temp=thread_list[i]->schedData.next; + for(;;) + { + if(temp->flags.isReady()) + { + //Found a READY thread, so run this one + cur=temp; + #ifdef WITH_PROCESSES + if(const_cast(cur)->flags.isInUserspace()==false) + { + ctxsave=cur->ctxsave; + MPUConfiguration::IRQdisable(); + } else { + ctxsave=cur->userCtxsave; + //A kernel thread is never in userspace, so the cast is safe + static_cast(cur->proc)->mpu.IRQenable(); + } + #else //WITH_PROCESSES + ctxsave=temp->ctxsave; + #endif //WITH_PROCESSES + //Rotate to next thread so that next time the list is walked + //a different thread, if available, will be chosen first + thread_list[i]=temp; + return; + } else temp=temp->schedData.next; + if(temp==thread_list[i]->schedData.next) break; + } + } + //No thread found, run the idle thread + cur=idle; + ctxsave=idle->ctxsave; + #ifdef WITH_PROCESSES + MPUConfiguration::IRQdisable(); + #endif //WITH_PROCESSES +} + +Thread *PriorityScheduler::thread_list[PRIORITY_MAX]={0}; +Thread *PriorityScheduler::idle=0; + +} //namespace miosix + +#endif //SCHED_TYPE_PRIORITY diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.h b/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.h new file mode 100644 index 00000000..51004298 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler.h @@ -0,0 +1,157 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef PRIORITY_SCHEDULER_H +#define PRIORITY_SCHEDULER_H + +#include "config/miosix_settings.h" +#include "priority_scheduler_types.h" +#include "kernel/kernel.h" + +#ifdef SCHED_TYPE_PRIORITY + +namespace miosix { + +/** + * \internal + * Priority scheduler. + */ +class PriorityScheduler +{ +public: + /** + * \internal + * Add a new thread to the scheduler. + * This is called when a thread is created. + * \param thread a pointer to a valid thread instance. + * The behaviour is undefined if a thread is added multiple timed to the + * scheduler, or if thread is NULL. + * \param priority the priority of the new thread. + * Priority must be a positive value. + * Note that the meaning of priority is scheduler specific. + */ + static bool PKaddThread(Thread *thread, PrioritySchedulerPriority priority); + + /** + * \internal + * \return true if thread exists, false if does not exist or has been + * deleted. A joinable thread is considered existing until it has been + * joined, even if it returns from its entry point (unless it is detached + * and terminates). + * + * Can be called both with the kernel paused and with interrupts disabled. + */ + static bool PKexists(Thread *thread); + + /** + * \internal + * Called when there is at least one dead thread to be removed from the + * scheduler + */ + static void PKremoveDeadThreads(); + + /** + * \internal + * Set the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be changed. + * \param newPriority new thread priority. + * Priority must be a positive value. + */ + static void PKsetPriority(Thread *thread, + PrioritySchedulerPriority newPriority); + + /** + * \internal + * Get the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static PrioritySchedulerPriority getPriority(Thread *thread) + { + return thread->schedData.priority; + } + + /** + * \internal + * Same as getPriority, but meant to be called with interrupts disabled. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static PrioritySchedulerPriority IRQgetPriority(Thread *thread) + { + return thread->schedData.priority; + } + + /** + * \internal + * This is called before the kernel is started to by the kernel. The given + * thread is the idle thread, to be run all the times where no other thread + * can run. + */ + static void IRQsetIdleThread(Thread *idleThread); + + /** + * \internal + * This member function is called by the kernel every time a thread changes + * its running status. For example when a thread become sleeping, waiting, + * deleted or if it exits the sleeping or waiting status + */ + static void IRQwaitStatusHook() {} + + /** + * \internal + * This function is used to develop interrupt driven peripheral drivers.
+ * Can be used ONLY inside an IRQ (and not when interrupts are disabled) to + * find next thread in READY status. If the kernel is paused, does nothing. + * Can be used for example if an IRQ causes a higher priority thread to be + * woken, to change context. Note that to use this function the IRQ must + * use the macros to save/restore context defined in portability.h + * + * If the kernel is paused does nothing. + * It's behaviour is to modify the global variable miosix::cur which always + * points to the currently running thread. + */ + static void IRQfindNextThread(); + +private: + + ///\internal Vector of lists of threads, there's one list for each priority + ///Each list s a circular list. + ///(since 0=NULL, using aggregate initialization) + static Thread *thread_list[PRIORITY_MAX]; + + ///\internal idle thread + static Thread *idle; +}; + +} //namespace miosix + +#endif //SCHED_TYPE_PRIORITY + +#endif //PRIORITY_SCHEDULER_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler_types.h b/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler_types.h new file mode 100644 index 00000000..e58880c9 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/priority/priority_scheduler_types.h @@ -0,0 +1,128 @@ +/*************************************************************************** + * Copyright (C) 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "config/miosix_settings.h" + +#ifndef PRIORITY_SCHEDULER_TYPES_H +#define PRIORITY_SCHEDULER_TYPES_H + +#ifdef SCHED_TYPE_PRIORITY + +namespace miosix { + +class Thread; //Forward declaration + +/** + * This class models the concept of priority for the priority scheduler. + * In this scheduler the priority is simply a short int with values ranging + * from 0 to PRIORITY_MAX-1, higher values mean higher priority, and the special + * value -1 reserved for the idle thread. + */ +class PrioritySchedulerPriority +{ +public: + /** + * Constructor. Not explicit for backward compatibility. + * \param priority the desired priority value. + */ + PrioritySchedulerPriority(short int priority): priority(priority) {} + + /** + * Default constructor. + */ + PrioritySchedulerPriority(): priority(MAIN_PRIORITY) {} + + /** + * \return the priority value + */ + short int get() const { return priority; } + + /** + * \return true if this objects represents a valid priority. + * Note that the value -1 is considered not valid, because it is reserved + * for the idle thread. + */ + bool validate() const + { + return this->priority>=0 && this->priority(PrioritySchedulerPriority a, PrioritySchedulerPriority b) +{ + return a.get() > b.get(); +} + +inline bool operator >=(PrioritySchedulerPriority a, PrioritySchedulerPriority b) +{ + return a.get() >= b.get(); +} + +inline bool operator ==(PrioritySchedulerPriority a, PrioritySchedulerPriority b) +{ + return a.get() == b.get(); +} + +inline bool operator !=(PrioritySchedulerPriority a, PrioritySchedulerPriority b) +{ + return a.get() != b.get(); +} + +/** + * \internal + * An instance of this class is embedded in every Thread class. It contains all + * the per-thread data required by the scheduler. + */ +class PrioritySchedulerData +{ +public: + ///Thread priority. Used to speed up the implementation of getPriority.
+ ///Note that to change the priority of a thread it is not enough to change + ///this.
It is also necessary to move the thread from the old prority + ///list to the new priority list. + PrioritySchedulerPriority priority; + Thread *next;/// * + ***************************************************************************/ + +#include "config/miosix_settings.h" +#include "kernel/scheduler/priority/priority_scheduler_types.h" +#include "kernel/scheduler/control/control_scheduler_types.h" +#include "kernel/scheduler/edf/edf_scheduler_types.h" + +#ifndef SCHED_TYPES_H +#define SCHED_TYPES_H + +namespace miosix { + +#ifdef SCHED_TYPE_PRIORITY +typedef PrioritySchedulerPriority Priority; +typedef PrioritySchedulerData SchedulerData; +#elif defined(SCHED_TYPE_CONTROL_BASED) +typedef ControlSchedulerPriority Priority; +typedef ControlSchedulerData SchedulerData; +#elif defined(SCHED_TYPE_EDF) +typedef EDFSchedulerPriority Priority; +typedef EDFSchedulerData SchedulerData; +#else +#error No scheduler selected in config/miosix_settings.h +#endif + +} //namespace miosix + +#endif //SCHED_TYPES_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/scheduler.h b/lib/miosix-kernel/miosix/kernel/scheduler/scheduler.h new file mode 100644 index 00000000..08e1f162 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/scheduler.h @@ -0,0 +1,186 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SCHEDULER_H +#define SCHEDULER_H + +#include "config/miosix_settings.h" +#include "kernel/scheduler/priority/priority_scheduler.h" +#include "kernel/scheduler/control/control_scheduler.h" +#include "kernel/scheduler/edf/edf_scheduler.h" + +namespace miosix { + +class Thread; //Forward declaration + +/** + * \internal + * This class is the common interface between the kernel and the scheduling + * algorithms. + * Dispatching of the calls to the implementation is done using templates + * instead of inheritance and virtual functions beacause the scheduler + * implementation is chosen at compile time. + */ +template +class basic_scheduler +{ +public: + + /** + * \internal + * Add a new thread to the scheduler. + * \param thread a pointer to a valid thread instance. + * The behaviour is undefined if a thread is added multiple timed to the + * scheduler, or if thread is NULL. + * \param priority the priority of the new thread. + * Priority must be a positive value. + * Note that the meaning of priority is scheduler specific. + * \return false if an error occurred and the thread could not be added to + * the scheduler + * + * Note: this member function is called also before the kernel is started + * to add the main and idle thread. + */ + static bool PKaddThread(Thread *thread, Priority priority) + { + return T::PKaddThread(thread,priority); + } + + /** + * \internal + * \return true if thread exists, false if does not exist or has been + * deleted. A joinable thread is considered existing until it has been + * joined, even if it returns from its entry point (unless it is detached + * and terminates). + * + * Can be called both with the kernel paused and with interrupts disabled. + */ + static bool PKexists(Thread *thread) + { + return T::PKexists(thread); + } + + /** + * \internal + * Called when there is at least one dead thread to be removed from the + * scheduler + */ + static void PKremoveDeadThreads() + { + T::PKremoveDeadThreads(); + } + + /** + * \internal + * Set the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be changed. + * \param newPriority new thread priority. + * Priority must be a positive value. + */ + static void PKsetPriority(Thread *thread, Priority newPriority) + { + T::PKsetPriority(thread,newPriority); + } + + /** + * \internal + * Get the priority of a thread. + * Note that the meaning of priority is scheduler specific. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static Priority getPriority(Thread *thread) + { + return T::getPriority(thread); + } + + /** + * \internal + * Same as getPriority, but meant to be called with interrupts disabled. + * \param thread thread whose priority needs to be queried. + * \return the priority of thread. + */ + static Priority IRQgetPriority(Thread *thread) + { + return T::IRQgetPriority(thread); + } + + /** + * \internal + * This is called before the kernel is started to by the kernel. The given + * thread is the idle thread, to be run all the times where no other thread + * can run. + */ + static void IRQsetIdleThread(Thread *idleThread) + { + return T::IRQsetIdleThread(idleThread); + } + + /** + * \internal + * This member function is called by the kernel every time a thread changes + * its running status. For example when a thread become sleeping, waiting, + * deleted or if it exits the sleeping or waiting status + */ + static void IRQwaitStatusHook() + { + T::IRQwaitStatusHook(); + } + + /** + * This function is used to develop interrupt driven peripheral drivers.
+ * Can be used ONLY inside an IRQ (and not when interrupts are disabled) to + * find next thread in READY status. If the kernel is paused, does nothing. + * Can be used for example if an IRQ causes a higher priority thread to be + * woken, to change context. Note that to use this function the IRQ must + * use the macros to save/restore context defined in portability.h + * + * If the kernel is paused does nothing. + * It's behaviour is to modify the global variable miosix::cur which always + * points to the currently running thread. + */ + static void IRQfindNextThread() + { + T::IRQfindNextThread(); + } + +}; + +#ifdef SCHED_TYPE_PRIORITY +typedef basic_scheduler Scheduler; +#elif defined(SCHED_TYPE_CONTROL_BASED) +typedef basic_scheduler Scheduler; +#elif defined(SCHED_TYPE_EDF) +typedef basic_scheduler Scheduler; +#else +#error No scheduler selected in config/miosix_settings.h +#endif + +} //namespace miosix + +#endif //SCHEDULER_H diff --git a/lib/miosix-kernel/miosix/kernel/scheduler/tick_interrupt.h b/lib/miosix-kernel/miosix/kernel/scheduler/tick_interrupt.h new file mode 100644 index 00000000..84d096b2 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/scheduler/tick_interrupt.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2010 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef TICK_INTERRUPT_H +#define TICK_INTERRUPT_H + +#include "config/miosix_settings.h" +#include "scheduler.h" + +namespace miosix { + +//These are a couple of global variables and a function that are part of the +//internal implementation of the kernel and are defined in kernel.cpp +//User code should not know about these nor try to use them. +extern volatile int kernel_running;///\internal Do not use outside the kernel +extern volatile bool tick_skew;///\internal Do not use outside the kernel +extern volatile Thread *cur;///\internal Do not use outside the kernel +extern bool IRQwakeThreads();///\internal Do not use outside the kernel + +inline void IRQtickInterrupt() +{ + bool woken=IRQwakeThreads();//Increment tick and wake threads,if any + (void)woken; //Avoid unused variable warning. + + #ifdef SCHED_TYPE_PRIORITY + //With the priority scheduler every tick causes a context switck + Scheduler::IRQfindNextThread();//If the kernel is running, preempt + if(kernel_running!=0) tick_skew=true; + #elif defined(SCHED_TYPE_CONTROL_BASED) + //Normally, with the control based scheduler, preemptions do not happen + //here, but in the auxiliary timer interrupt to take into account variable + //bursts. But there is one exception: when a thread wakes up from sleep + //and the idle thread is running. + if(woken && cur==ControlScheduler::IRQgetIdleThread()) + { + Scheduler::IRQfindNextThread(); + if(kernel_running!=0) tick_skew=true; + } + #elif defined(SCHED_TYPE_EDF) + //With the EDF scheduler a preemption happens only if a thread with a closer + //deadline appears. So by deafult there is no need to call the scheduler; + //only if some threads were woken, they may have closer deadlines + if(woken) + { + Scheduler::IRQfindNextThread(); + if(kernel_running!=0) tick_skew=true; + } + #endif +} + +} + +#endif //TICK_INTERRUPT_H diff --git a/lib/miosix-kernel/miosix/kernel/stage_2_boot.cpp b/lib/miosix-kernel/miosix/kernel/stage_2_boot.cpp new file mode 100644 index 00000000..cbd6d6c2 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/stage_2_boot.cpp @@ -0,0 +1,133 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +/* ***************************************************** +Miosix boot system +Stage 2 boot process +This code will initialize system peripherals, and will +start the kernel and filesystem. +***************************************************** */ + +#include +#include +// Low level hardware functionalities +#include "interfaces/bsp.h" +// Miosix kernel +#include "kernel.h" +#include "filesystem/file_access.h" +#include "error.h" +#include "logging.h" +// settings for miosix +#include "config/miosix_settings.h" +#include "util/util.h" +#include "util/version.h" + +using namespace std; + +///<\internal Entry point for application code. +int main(int argc, char *argv[]); + +namespace miosix { + +/** + * \internal + * Calls C++ global constructors + * \param start first function pointer to call + * \param end one past the last function pointer to call + */ +static void callConstructors(unsigned long *start, unsigned long *end) +{ + for(unsigned long *i=start; i(*i); + funcptr(); + } +} + +void *mainLoader(void *argv) +{ + (void) argv; + + //If reaches here kernel is started, print Ok + bootlog("Ok\n%s\n",getMiosixVersion()); + + //Starting part of bsp that must be started after kernel + bspInit2(); + + //Initialize application C++ global constructors (called after boot) + extern unsigned long __preinit_array_start asm("__preinit_array_start"); + extern unsigned long __preinit_array_end asm("__preinit_array_end"); + extern unsigned long __init_array_start asm("__init_array_start"); + extern unsigned long __init_array_end asm("__init_array_end"); + extern unsigned long _ctor_start asm("_ctor_start"); + extern unsigned long _ctor_end asm("_ctor_end"); + callConstructors(&__preinit_array_start, &__preinit_array_end); + callConstructors(&__init_array_start, &__init_array_end); + callConstructors(&_ctor_start, &_ctor_end); + + bootlog("Available heap %d out of %d Bytes\n", + MemoryProfiling::getCurrentFreeHeap(), + MemoryProfiling::getHeapSize()); + + //Run application code + #ifdef __NO_EXCEPTIONS + main(0,NULL); + #else //__NO_EXCEPTIONS + try { + main(0,NULL); + } catch(std::exception& e) { + errorLog("***An exception propagated through a thread\n"); + errorLog("what():%s\n",e.what()); + } catch(...) { + errorLog("***An exception propagated through a thread\n"); + } + #endif //__NO_EXCEPTIONS + + //If main returns, shutdown + shutdown(); + return 0; +} + +} //namespace miosix + +extern "C" void _init() +{ + using namespace miosix; + + //Initialize kernel C++ global constructors (called before boot) + extern unsigned long __miosix_init_array_start asm("__miosix_init_array_start"); + extern unsigned long __miosix_init_array_end asm("__miosix_init_array_end"); + callConstructors(&__miosix_init_array_start, &__miosix_init_array_end); + + if(areInterruptsEnabled()) errorHandler(INTERRUPTS_ENABLED_AT_BOOT); + IRQbspInit(); + //After IRQbspInit() serial port is initialized, so we can use IRQbootlog + IRQbootlog("Starting Kernel... "); + startKernel(); + //Never reach here (unless startKernel fails) +} diff --git a/lib/miosix-kernel/miosix/kernel/stage_2_boot.h b/lib/miosix-kernel/miosix/kernel/stage_2_boot.h new file mode 100644 index 00000000..5cff2abc --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/stage_2_boot.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef STAGE_2_BOOT_H +#define STAGE_2_BOOT_H + +#ifndef COMPILING_MIOSIX +#error "This is header is private, it can't be used outside Miosix itself." +#error "If your code depends on a private header, it IS broken." +#endif //COMPILING_MIOSIX + +namespace miosix { + +/** + * \internal + * This function will perform the part of system initialization that must be + * done after the kernel is started. At the end, it will call main() + * \param argv ignored parameter + */ +void *mainLoader(void *argv); + +} //namespace miosix + +/** + * \internal + * Performs the part of initialization that must be done before the kernel is + * started, and starts the kernel. + * This function is called by the stage 1 boot which is architecture dependent. + */ +extern "C" void _init(); + +#endif //STAGE_2_BOOT_H diff --git a/lib/miosix-kernel/miosix/kernel/sync.cpp b/lib/miosix-kernel/miosix/kernel/sync.cpp new file mode 100644 index 00000000..d485a7db --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/sync.cpp @@ -0,0 +1,516 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#include "sync.h" +#include "kernel.h" +#include "kernel/scheduler/scheduler.h" +#include "error.h" +#include "pthread_private.h" +#include + +using namespace std; + +namespace miosix { + +// +// class Mutex +// + +Mutex::Mutex(Options opt): owner(0), next(0), waiting() +{ + recursiveDepth= opt==RECURSIVE ? 0 : -1; +} + +void Mutex::PKlock(PauseKernelLock& dLock) +{ + Thread *p=Thread::getCurrentThread(); + if(owner==0) + { + owner=p; + //Save original thread priority, if the thread has not yet locked + //another mutex + if(owner->mutexLocked==0) owner->savedPriority=owner->getPriority(); + //Add this mutex to the list of mutexes locked by owner + this->next=owner->mutexLocked; + owner->mutexLocked=this; + return; + } + + //This check is very important. Without this attempting to lock the same + //mutex twice won't cause a deadlock because the Thread::IRQwait() is + //enclosed in a while(owner!=p) which is immeditely false. + if(owner==p) + { + if(recursiveDepth>=0) + { + recursiveDepth++; + return; + } else errorHandler(MUTEX_DEADLOCK); //Bad, deadlock + } + + //Add thread to mutex' waiting queue + waiting.push_back(p); + LowerPriority l; + push_heap(waiting.begin(),waiting.end(),l); + + //Handle priority inheritance + if(p->mutexWaiting!=0) errorHandler(UNEXPECTED); + p->mutexWaiting=this; + if(p->getPriority()>owner->getPriority()) + { + Thread *walk=owner; + for(;;) + { + Scheduler::PKsetPriority(walk,p->getPriority()); + if(walk->mutexWaiting==0) break; + make_heap(walk->mutexWaiting->waiting.begin(), + walk->mutexWaiting->waiting.end(),l); + walk=walk->mutexWaiting->owner; + } + } + + //The while is necessary because some other thread might call wakeup() + //on this thread. So the thread can wakeup also for other reasons not + //related to the mutex becoming free + while(owner!=p) + { + //Wait can only be called with kernel started, while IRQwait can + //only be called with interupts disabled, so that's why interrupts + //are disabled + { + FastInterruptDisableLock l; + Thread::IRQwait();//Return immediately + } + { + RestartKernelLock eLock(dLock); + //Now the IRQwait becomes effective + Thread::yield(); + } + } +} + +void Mutex::PKlockToDepth(PauseKernelLock& dLock, unsigned int depth) +{ + Thread *p=Thread::getCurrentThread(); + if(owner==0) + { + owner=p; + if(recursiveDepth>=0) recursiveDepth=depth; + //Save original thread priority, if the thread has not yet locked + //another mutex + if(owner->mutexLocked==0) owner->savedPriority=owner->getPriority(); + //Add this mutex to the list of mutexes locked by owner + this->next=owner->mutexLocked; + owner->mutexLocked=this; + return; + } + + //This check is very important. Without this attempting to lock the same + //mutex twice won't cause a deadlock because the Thread::IRQwait() is + //enclosed in a while(owner!=p) which is immeditely false. + if(owner==p) + { + if(recursiveDepth>=0) + { + recursiveDepth=depth; + return; + } else errorHandler(MUTEX_DEADLOCK); //Bad, deadlock + } + + //Add thread to mutex' waiting queue + waiting.push_back(p); + LowerPriority l; + push_heap(waiting.begin(),waiting.end(),l); + + //Handle priority inheritance + if(p->mutexWaiting!=0) errorHandler(UNEXPECTED); + p->mutexWaiting=this; + if(p->getPriority()>owner->getPriority()) + { + Thread *walk=owner; + for(;;) + { + Scheduler::PKsetPriority(walk,p->getPriority()); + if(walk->mutexWaiting==0) break; + make_heap(walk->mutexWaiting->waiting.begin(), + walk->mutexWaiting->waiting.end(),l); + walk=walk->mutexWaiting->owner; + } + } + + //The while is necessary because some other thread might call wakeup() + //on this thread. So the thread can wakeup also for other reasons not + //related to the mutex becoming free + while(owner!=p) + { + //Wait can only be called with kernel started, while IRQwait can + //only be called with interupts disabled, so that's why interrupts + //are disabled + { + FastInterruptDisableLock l; + Thread::IRQwait();//Return immediately + } + { + RestartKernelLock eLock(dLock); + //Now the IRQwait becomes effective + Thread::yield(); + } + } + if(recursiveDepth>=0) recursiveDepth=depth; +} + +bool Mutex::PKtryLock(PauseKernelLock& dLock) +{ + (void) dLock; + + Thread *p=Thread::getCurrentThread(); + if(owner==0) + { + owner=p; + //Save original thread priority, if the thread has not yet locked + //another mutex + if(owner->mutexLocked==0) owner->savedPriority=owner->getPriority(); + //Add this mutex to the list of mutexes locked by owner + this->next=owner->mutexLocked; + owner->mutexLocked=this; + return true; + } + if(owner==p && recursiveDepth>=0) + { + recursiveDepth++; + return true; + } + return false; +} + +bool Mutex::PKunlock(PauseKernelLock& dLock) +{ + (void) dLock; + + Thread *p=Thread::getCurrentThread(); + if(owner!=p) return false; + + if(recursiveDepth>0) + { + recursiveDepth--; + return false; + } + + //Remove this mutex from the list of mutexes locked by the owner + if(owner->mutexLocked==this) + { + owner->mutexLocked=owner->mutexLocked->next; + } else { + Mutex *walk=owner->mutexLocked; + for(;;) + { + //this Mutex not in owner's list? impossible + if(walk->next==0) errorHandler(UNEXPECTED); + if(walk->next==this) + { + walk->next=walk->next->next; + break; + } + walk=walk->next; + } + } + + //Handle priority inheritance + if(owner->mutexLocked==0) + { + //Not locking any other mutex + if(owner->savedPriority!=owner->getPriority()) + Scheduler::PKsetPriority(owner,owner->savedPriority); + } else { + Priority pr=owner->savedPriority; + //Calculate new priority of thread, which is + //max(savedPriority, inheritedPriority) + Mutex *walk=owner->mutexLocked; + while(walk!=0) + { + if(walk->waiting.empty()==false) + pr=max(pr,walk->waiting.front()->getPriority()); + walk=walk->next; + } + if(pr!=owner->getPriority()) Scheduler::PKsetPriority(owner,pr); + } + + //Choose next thread to lock the mutex + if(waiting.empty()==false) + { + //There is at least another thread waiting + owner=waiting.front(); + LowerPriority l; + pop_heap(waiting.begin(),waiting.end(),l); + waiting.pop_back(); + if(owner->mutexWaiting!=this) errorHandler(UNEXPECTED); + owner->mutexWaiting=0; + owner->PKwakeup(); + if(owner->mutexLocked==0) owner->savedPriority=owner->getPriority(); + //Add this mutex to the list of mutexes locked by owner + this->next=owner->mutexLocked; + owner->mutexLocked=this; + //Handle priority inheritance of new owner + if(waiting.empty()==false && + waiting.front()->getPriority()>owner->getPriority()) + Scheduler::PKsetPriority(owner,waiting.front()->getPriority()); + return owner->getPriority() > p->getPriority(); + } else { + owner=0; //No threads waiting + std::vector().swap(waiting); //Save some RAM + return false; + } +} + +unsigned int Mutex::PKunlockAllDepthLevels(PauseKernelLock& dLock) +{ + (void) dLock; + + Thread *p=Thread::getCurrentThread(); + if(owner!=p) return 0; + + //Remove this mutex from the list of mutexes locked by the owner + if(owner->mutexLocked==this) + { + owner->mutexLocked=owner->mutexLocked->next; + } else { + Mutex *walk=owner->mutexLocked; + for(;;) + { + //this Mutex not in owner's list? impossible + if(walk->next==0) errorHandler(UNEXPECTED); + if(walk->next==this) + { + walk->next=walk->next->next; + break; + } + walk=walk->next; + } + } + + //Handle priority inheritance + if(owner->mutexLocked==0) + { + //Not locking any other mutex + if(owner->savedPriority!=owner->getPriority()) + Scheduler::PKsetPriority(owner,owner->savedPriority); + } else { + Priority pr=owner->savedPriority; + //Calculate new priority of thread, which is + //max(savedPriority, inheritedPriority) + Mutex *walk=owner->mutexLocked; + while(walk!=0) + { + if(walk->waiting.empty()==false) + pr=max(pr,walk->waiting.front()->getPriority()); + walk=walk->next; + } + if(pr!=owner->getPriority()) Scheduler::PKsetPriority(owner,pr); + } + + //Choose next thread to lock the mutex + if(waiting.empty()==false) + { + //There is at least another thread waiting + owner=waiting.front(); + LowerPriority l; + pop_heap(waiting.begin(),waiting.end(),l); + waiting.pop_back(); + if(owner->mutexWaiting!=this) errorHandler(UNEXPECTED); + owner->mutexWaiting=0; + owner->PKwakeup(); + if(owner->mutexLocked==0) owner->savedPriority=owner->getPriority(); + //Add this mutex to the list of mutexes locked by owner + this->next=owner->mutexLocked; + owner->mutexLocked=this; + //Handle priority inheritance of new owner + if(waiting.empty()==false && + waiting.front()->getPriority()>owner->getPriority()) + Scheduler::PKsetPriority(owner,waiting.front()->getPriority()); + } else { + owner=0; //No threads waiting + std::vector().swap(waiting); //Save some RAM + } + + if(recursiveDepth<0) return 0; + unsigned int result=recursiveDepth; + recursiveDepth=0; + return result; +} + +// +// class ConditionVariable +// + +ConditionVariable::ConditionVariable(): first(0), last(0) {} + +void ConditionVariable::wait(Mutex& m) +{ + PauseKernelLock dLock; + + WaitingData w; + w.p=Thread::getCurrentThread(); + w.next=0; + //Add entry to tail of list + if(first==0) + { + first=last=&w; + } else { + last->next=&w; + last=&w; + } + //Unlock mutex and wait + { + FastInterruptDisableLock l; + w.p->flags.IRQsetCondWait(true); + } + + unsigned int depth=m.PKunlockAllDepthLevels(dLock); + { + RestartKernelLock eLock(dLock); + Thread::yield(); //Here the wait becomes effective + } + m.PKlockToDepth(dLock,depth); +} + +void ConditionVariable::wait(FastMutex& m) +{ + FastInterruptDisableLock dLock; + + WaitingData w; + w.p=Thread::getCurrentThread(); + w.next=0; + //Add entry to tail of list + if(first==0) + { + first=last=&w; + } else { + last->next=&w; + last=&w; + } + //Unlock mutex and wait + w.p->flags.IRQsetCondWait(true); + + unsigned int depth=IRQdoMutexUnlockAllDepthLevels(m.get()); + { + FastInterruptEnableLock eLock(dLock); + Thread::yield(); //Here the wait becomes effective + } + IRQdoMutexLockToDepth(m.get(),dLock,depth); +} + +void ConditionVariable::signal() +{ + bool hppw=false; + { + //Using interruptDisableLock because we need to call IRQsetCondWait + //that can only be called with irq disabled, othrwise we would use + //PauseKernelLock + FastInterruptDisableLock lock; + if(first==0) return; + //Wakeup + first->p->flags.IRQsetCondWait(false); + //Check for priority issues + if(first->p->IRQgetPriority() > + Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true; + //Remove from list + first=first->next; + } + //If the woken thread has higher priority than our priority, yield + if(hppw) Thread::yield(); +} + +void ConditionVariable::broadcast() +{ + bool hppw=false; + { + //Using interruptDisableLock because we need to call IRQsetCondWait + //that can only be called with irq disabled, othrwise we would use + //PauseKernelLock + FastInterruptDisableLock lock; + while(first!=0) + { + //Wakeup + first->p->flags.IRQsetCondWait(false); + //Check for priority issues + if(first->p->IRQgetPriority() > + Thread::IRQgetCurrentThread()->IRQgetPriority()) hppw=true; + //Remove from list + first=first->next; + } + } + //If at least one of the woken thread has higher priority than our priority, + //yield + if(hppw) Thread::yield(); +} + +// +// class Timer +// + +Timer::Timer() +{ + first=true; + running=false; + start_tick=tick_count=0; +} + +void Timer::start() +{ + first=false; + running=true; + start_tick=getTick(); +} + +void Timer::stop() +{ + if(running==false) return; + running=false; + tick_count+=getTick()-start_tick; + start_tick=0; +} + +bool Timer::isRunning() const +{ + return running; +} + +int Timer::interval() const +{ + if((first==true)||(running==true)||(tick_count>2147483647)) return -1; + return (int)tick_count; +} + +void Timer::clear() +{ + first=true; + running=false; + start_tick=tick_count=0; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/kernel/sync.h b/lib/miosix-kernel/miosix/kernel/sync.h new file mode 100644 index 00000000..bd6feeae --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/sync.h @@ -0,0 +1,526 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 * + * by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + //Miosix kernel + +#ifndef SYNC_H +#define SYNC_H + +#include "kernel.h" +#include + +namespace miosix { + +/** + * \addtogroup Sync + * \{ + */ + +/** + * Fast mutex without support for priority inheritance + */ +class FastMutex +{ +public: + /** + * Mutex options, passed to the constructor to set additional options.
+ * The DEFAULT option indicates the default Mutex type. + */ + enum Options + { + DEFAULT, ///< Default mutex + RECURSIVE ///< Mutex is recursive + }; + + /** + * Constructor, initializes the mutex. + */ + FastMutex(Options opt=DEFAULT) + { + if(opt==RECURSIVE) + { + pthread_mutexattr_t temp; + pthread_mutexattr_init(&temp); + pthread_mutexattr_settype(&temp,PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&impl,&temp); + pthread_mutexattr_destroy(&temp); + } else pthread_mutex_init(&impl,NULL); + } + + /** + * Locks the critical section. If the critical section is already locked, + * the thread will be queued in a wait list. + */ + void lock() + { + pthread_mutex_lock(&impl); + } + + /** + * Acquires the lock only if the critical section is not already locked by + * other threads. Attempting to lock again a recursive mutex will fail, and + * the mutex' lock count will not be incremented. + * \return true if the lock was acquired + */ + bool tryLock() + { + return pthread_mutex_trylock(&impl)==0; + } + + /** + * Unlocks the critical section. + */ + void unlock() + { + pthread_mutex_unlock(&impl); + } + + /** + * \internal + * \return the FastMutex implementation defined mutex type + */ + pthread_mutex_t *get() + { + return &impl; + } + + /** + * Destructor + */ + ~FastMutex() + { + pthread_mutex_destroy(&impl); + } + +private: + FastMutex(const FastMutex&); + FastMutex& operator= (const FastMutex&); + + pthread_mutex_t impl; +}; + +//Forward declaration +class ConditionVariable; + +/** + * A mutex class with support for priority inheritance. If a thread tries to + * enter a critical section which is not free, it will be put to sleep and + * added to a queue of sleeping threads, ordered by priority. The thread that + * is into the critical section inherits the highest priority among the threads + * that are waiting if it is higher than its original priority.
+ * This mutex is meant to be a static or global class. Dynamically creating a + * mutex with new or on the stack must be done with care, to avoid deleting a + * locked mutex, and to avoid situations where a thread tries to lock a + * deleted mutex.
+ */ +class Mutex +{ +public: + /** + * Mutex options, passed to the constructor to set additional options.
+ * The DEFAULT option indicates the default Mutex type. + */ + enum Options + { + DEFAULT, ///< Default mutex + RECURSIVE ///< Mutex is recursive + }; + + /** + * Constructor, initializes the mutex. + */ + Mutex(Options opt=DEFAULT); + + /** + * Locks the critical section. If the critical section is already locked, + * the thread will be queued in a wait list. + */ + void lock() + { + PauseKernelLock dLock; + PKlock(dLock); + } + + /** + * Acquires the lock only if the critical section is not already locked by + * other threads. Attempting to lock again a recursive mutex will fail, and + * the mutex' lock count will not be incremented. + * \return true if the lock was acquired + */ + bool tryLock() + { + PauseKernelLock dLock; + return PKtryLock(dLock); + } + + /** + * Unlocks the critical section. + */ + void unlock() + { + #ifdef SCHED_TYPE_EDF + bool hppw; + { + PauseKernelLock dLock; + hppw=PKunlock(dLock); + } + if(hppw) Thread::yield();//The other thread might have a closer deadline + #else + { + PauseKernelLock dLock; + PKunlock(dLock); + } + #endif //SCHED_TYPE_EDF + } + +private: + //Unwanted methods + Mutex(const Mutex& s);///< No public copy constructor + Mutex& operator = (const Mutex& s);///< No publc operator = + //Uses default destructor + + /** + * Lock mutex, can be called only with kernel paused one level deep + * (pauseKernel calls can be nested). If another thread holds the mutex, + * this call will restart the kernel and wait (that's why the kernel must + * be paused one level deep).
+ * \param dLock the PauseKernelLock instance that paused the kernel. + */ + void PKlock(PauseKernelLock& dLock); + + /** + * Lock mutex to a given depth, can be called only with kernel paused one + * level deep (pauseKernel calls can be nested). If another thread holds the + * mutex, this call will restart the kernel and wait (that's why the kernel + * must be paused one level deep).
+ * If the mutex is not recursive the mutex is locked only one level deep + * regardless of the depth value. + * \param dLock the PauseKernelLock instance that paused the kernel. + * \param depth recursive depth at which the mutex will be locked. Zero + * means the mutex is locked one level deep (as if lock() was called once), + * one means two levels deep, etc. + */ + void PKlockToDepth(PauseKernelLock& dLock, unsigned int depth); + + /** + * Acquires the lock only if the critical section is not already locked by + * other threads. Attempting to lock again a recursive mutex will fail, and + * the mutex' lock count will not be incremented.
+ * Can be called only with kernel paused one level deep. + * (pauseKernel calls can be nested). + * \param dLock the PauseKernelLock instance that paused the kernel. + * \return true if the lock was acquired + */ + bool PKtryLock(PauseKernelLock& dLock); + + /** + * Unlock mutex, can be called only with kernel paused one level deep + * (pauseKernel calls can be nested).
+ * \param dLock the PauseKernelLock instance that paused the kernel. + * \return true if a higher priority thread was woken + */ + bool PKunlock(PauseKernelLock& dLock); + + /** + * Unlock mutex all levels of a recursive mutex, can be called only with + * kernel paused one level deep (pauseKernel calls can be nested).
+ * \param dLock the PauseKernelLock instance that paused the kernel. + * \return the mutex recursive depth (how many times it was locked by the + * owner). Zero means the mutex is locked one level deep (lock() was called + * once), one means two levels deep, etc. + */ + unsigned int PKunlockAllDepthLevels(PauseKernelLock& dLock); + + /// Thread currently inside critical section, if NULL the critical section + /// is free + Thread *owner; + + /// If this mutex is locked, it is added to a list of mutexes held by the + /// thread that owns this mutex. This field is necessary to make the list. + Mutex *next; + + /// Waiting thread are stored in this min-heap, sorted by priority + std::vector waiting; + + /// Used to hold nesting depth for recursive mutexes, -1 if not recursive + int recursiveDepth; + + //Friends + friend class ConditionVariable; + friend class Thread; +}; + +/** + * Very simple RAII style class to lock a mutex in an exception-safe way. + * Mutex is acquired by the constructor and released by the destructor. + */ +template +class Lock +{ +public: + /** + * Constructor: locks the mutex + * \param m mutex to lock + */ + explicit Lock(T& m): mutex(m) + { + mutex.lock(); + } + + /** + * Destructor: unlocks the mutex + */ + ~Lock() + { + mutex.unlock(); + } + + /** + * \return the locked mutex + */ + T& get() + { + return mutex; + } + +private: + //Unwanted methods + Lock(const Lock& l);///< No public copy constructor + Lock& operator = (const Lock& l);///< No publc operator = + + T& mutex;///< Reference to locked mutex +}; + +/** + * This class allows to temporarily re unlock a mutex in a scope where + * it is locked
+ * Example: + * \code + * Mutex m; + * + * //Mutex unlocked + * { + * Lock dLock(m); + * + * //Now mutex locked + * + * { + * Unlock eLock(dLock); + * + * //Now mutex back unlocked + * } + * + * //Now mutex again locked + * } + * //Finally mutex unlocked + * \endcode + */ +template +class Unlock +{ +public: + /** + * Constructor, unlock mutex. + * \param l the Lock that locked the mutex. + */ + explicit Unlock(Lock& l): mutex(l.get()) + { + mutex.unlock(); + } + + /** + * Constructor, unlock mutex. + * \param m a locked mutex. + */ + Unlock(T& m): mutex(m) + { + mutex.unlock(); + } + + /** + * Destructor. + * Disable back interrupts. + */ + ~Unlock() + { + mutex.lock(); + } + + /** + * \return the unlocked mutex + */ + T& get() + { + return mutex; + } + +private: + //Unwanted methods + Unlock(const Unlock& l); + Unlock& operator= (const Unlock& l); + + T& mutex;///< Reference to locked mutex +}; + +/** + * A condition variable class for thread synchronization, available from + * Miosix 1.53.
+ * One or more threads can wait on the condition variable, and the signal() + * and broadcast() allow to wake ne or all the waiting threads.
+ * This class is meant to be a static or global class. Dynamically creating a + * ConditionVariable with new or on the stack must be done with care, to avoid + * deleting a ConditionVariable while some threads are waiting, and to avoid + * situations where a thread tries to wait on a deleted ConditionVariable.
+ */ +class ConditionVariable +{ +public: + /** + * Constructor, initializes the ConditionVariable. + */ + ConditionVariable(); + + /** + * Unlock the mutex and wait. + * If more threads call wait() they must do so specifying the same mutex, + * otherwise the behaviour is undefined. + * \param l A Lock instance that locked a Mutex + */ + template + void wait(Lock& l) + { + wait(l.get()); + } + + /** + * Unlock the Mutex and wait. + * If more threads call wait() they must do so specifying the same mutex, + * otherwise the behaviour is undefined. + * \param m a locked Mutex + */ + void wait(Mutex& m); + + /** + * Unlock the FastMutex and wait. + * If more threads call wait() they must do so specifying the same mutex, + * otherwise the behaviour is undefined. + * \param m a locked Mutex + */ + void wait(FastMutex& m); + + /** + * Wakeup one waiting thread. + * Currently implemented policy is fifo. + */ + void signal(); + + /** + * Wakeup all waiting threads. + */ + void broadcast(); + +private: + //Unwanted methods + ConditionVariable(const ConditionVariable& ); + ConditionVariable& operator= (const ConditionVariable& ); + + /** + * \internal + * \struct WaitingData + * This struct is used to make a list of waiting threads. + */ + struct WaitingData + { + Thread *p;///<\internal Thread that is waiting + WaitingData *next;///<\internal Next thread in the list + }; + + WaitingData *first;///Its resolution equals + * the kernel tick.
Maximum interval is 2^31-1 ticks. + */ +class Timer +{ +public: + /** + * Constructor. Timer is initialized in stopped status. + */ + Timer(); + + /** + * Start the timer + */ + void start(); + + /** + * Stop the timer. After stop, Timer can be started and stopped again to + * count non-contiguous timer intervals. + */ + void stop(); + + /** + * \return true if timer is running + */ + bool isRunning() const; + + /** + * get the interval, in kernel ticks. + * \return the number of tick between start and stop. Returns -1 if the + * timer was never started, if interval is called after start but before + * stop, or if it overflowed. + * + * To read the vaue of a timer without stopping it, you can use its copy + * constructor to create another timer, and stop it while the first timer + * keeps running. + */ + int interval() const; + + /** + * Clear the timer and set it to not running state. + */ + void clear(); + + //Using default copy constructor, operator = and destructor +private: + //Timer data + bool first;///< True if start has never been called + bool running;///< True if timer is running + long long start_tick;///< The kernel tick when start was called. + long long tick_count;///< The tick count +}; + +/** + * \} + */ + +} //namespace miosix + +#endif //SYNC_H diff --git a/lib/miosix-kernel/miosix/kernel/timeconversion.cpp b/lib/miosix-kernel/miosix/kernel/timeconversion.cpp new file mode 100644 index 00000000..e44805b6 --- /dev/null +++ b/lib/miosix-kernel/miosix/kernel/timeconversion.cpp @@ -0,0 +1,586 @@ +/*************************************************************************** + * Copyright (C) 2015, 2016 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "timeconversion.h" + +#ifdef TEST_ALGORITHM + +#include +#include +#include +#include +#include + +static bool print=true; +#define P(x) if(print) std::cout<<#x<<'='<>32; } + +/** + * \param a 32 bit unsigned number + * \param b 32 bit unsigned number + * \return a * b as a 64 unsigned number + */ +static inline unsigned long long mul32x32to64(unsigned int a, unsigned int b) +{ + //Casts are to produce a 64 bit result. Compiles to a single asm instruction + //in processors having 32x32 multiplication with 64 bit result + return static_cast(a)*static_cast(b); +} + +unsigned long long mul64x32d32(unsigned long long a, + unsigned int bi, unsigned int bf) +{ + /* + * The implemntation is a standard multiplication algorithm: + * | 64bit | 32bit | Frac part + * ---------------------------------------------------------------------- + * | hi(a) | lo(a) | 0 x + * | | bi | bf = + * ====================================================================== + * | hi(bi*lo(a)) | lo(bi*lo(a)) | 0 | - + * hi(bi*hi(a)) | +lo(bi*hi(a)) | | | + * | | +hi(bf*lo(a)) | lo(bf*lo(a)) | 0 + * | +hi(bf*hi(a)) | +lo(bf*hi(a)) | | + * ---------------------------------------------------------------------- + * 96bit | 64bit | 32bit | Frac part + * (Discarded) | <------ returned part ------> | (Disacrded) + * + * Note that [hi(bi*lo(a))|lo(bi*lo(a))] and [hi(bf*hi(a))|lo(bf*hi(a))] + * are two 64 bit numbers with the same alignment as the result, so + * result can be rewritten as + * bi*lo(a) + bf*hi(a) + hi(bf*lo(a)) + lo(bi*hi(a))<<32 + * + * The arithmetic rounding is implemented by adding one to the result if + * lo(bf*lo(a)) has bit 31 set. That is, if the fractional part is >=0.5. + * + * This code (without arithmetic rounding) takes around 30..40 clock cycles + * on Cortex-A9 (arm/thumb2), Cortex-M3 (thumb2), Cortex-M4 (thumb2), + * ARM7TDMI (arm), with GCC 4.7.3 and optimization levels -Os, -O2, -O3. + * + * TODO: this code is *much* slower with architectures missing a 32x32 + * multiplication with 64 bit result, probably in the thousands of clock + * cycles. An example arch is the Cortex-M0. How to deal with those arch? + */ + + unsigned int aLo=lo(a); + unsigned int aHi=hi(a); + + unsigned long long result=mul32x32to64(bi,aLo); + result+=mul32x32to64(bf,aHi); + + unsigned long long bfaLo=mul32x32to64(bf,aLo); + result+=hi(bfaLo); + + //Uncommenting this adds arithmetic rounding (round to nearest value). + //Leaving it commented out always rounds towards lowest and makes the + //algorithm significantly faster by not requiring a branch + //if(bfaLo & 0x80000000) result++; + + //This is a 32x32 -> 32 multiplication (upper 32 bits disacrded), with + //the 32 bits shifted to the upper word of a 64 bit number + result+=static_cast(bi*aHi)<<32; + + //Caller is responsible to never call this with values that produce overflow + return result; +} + +/** + * \param x a long long + * \return true if x>=0 + */ +static inline bool sign(long long x) +{ + return x>=0; +} + +/** + * \param x a long long + * \return |x| + * + * Note that compared to llabs this function returns an unsigned number, and + * is also sure to be inlined + */ +static inline unsigned long long uabs(long long x) +{ + long long result= sign(x) ? x : -x; + return static_cast(result); +} + +/* + * A note on the issues of tick to nanosecond conversion and back. + * Converting a number in tick to nanoseconds requires multiplying the ticks by + * a coefficient M, while doing the opposite requires a multiplication by 1/M. + * This code assumes that the tick frequency is less than 1GHz, so that M > 1, + * and thus 1/M < 1. Doing an exact multiplication would be too expensive + * computationally, so the coefficients are rounded in a 32.32 fixed point + * representation. In this representation, numbers are in the form + * A+B/2^32, where A and B are two unsigned 32 bit numbers. + * + * The essential requirements on this approximation are two: + * - the error of the conversion from tick to nanosecond should be as small + * as possible + * - if a value in ticks is converted to nanoseconds and then converted back to + * ticks, the result in ticks should be again the original value in ticks + * that we started with, with a very small error (say less than two ticks). + * For this reason, the error for the conversion from nanosecond to tick + * should be equal and opposite in sign to the tick to nanosecond error. + * + * The reason for the first requirement is easy to understand, the tick to + * nanosecond conversion is used when reading the hardware clock, so errors + * in this conversion result in errors in time measurement. + * Also, this requirement is easy to achieve with the given representation, + * as the M coefficient is greater than 1, and a fixed point representation + * has a high resolution when storing large numbers. + * Tests done with TEST_ALGORITHM have shown that the tick to nanosecond is + * zero for some relevant frequencies, and is generally below 0.04ppm, + * or an error that accumulates at a rate of 1.26 seconds per year of uptime, + * which is lower than that of most clock crystals. + * + * The second requirement requires some more explanation. Assume a typical use + * case for the timing subsystem: a task gets the current time, adds a small + * delay, say 1ms, and does an absolute sleep till that time point. + * The former operation, getting the time, requires a tick to nanosecond + * conversion, while the latter a nanosecond to tick for setting the interrupt. + * If these two conversions, that are here called a "round trip" would introduce + * an error of many ticks, that would make the sleep imprecise. At first + * this may seem a non-issue, as the errors are in the parts per million range, + * but the key point is that we are working with absolute times. So after + * one year of uptime, a mere mismatch of 0.04ppm between the two + * conversion coefficients will make our 1ms sleep become a 1261ms one, + * or -depending on the error sign- a sleep in the past! + * + * This issue is also difficult to solve, as the back conversion coefficient is + * less than 1, and the fixed point stores small numbers with less resolution, + * so the error of the nanosecond to tick conversion can grow as large as + * 10ppm for tick frequencies in the 10KHz range. The solution to this problem + * is twofold. First, an offline optimization of the back conversion coefficient + * is done int the TimeConversion constructor, with the aim of having its error + * as much as possible equal and opposite in sign to the tick to nanosecond + * error. + * Then, an online round trip adjustment is done in ns2tick(), by computing the + * round trip offset and subtracting it to the conversion, in order to make + * sure the round trip error is less than two ticks. + * As the online adjustment is a little expensive, the result is cached, and for + * conversion which are "near" to the one at the previous nanosecond to tick + * call the same offset is used. The range that is considered "near" is + * again computed offline, as it depends on how good the offline optimization + * was at making one conversion coefficient the opposite of the other, but is + * generally in the range of a few seconds to a few tens of seconds. + */ + +// +// class TimeConversion +// + +long long TimeConversion::ns2tick(long long ns) +{ + /* + * This algorithm does the online adjustment to compensate for round trip + * error that accumulates over time. It is probably the least intuitive + * algorithm in this file, so it requires careful explanation. + * If we are close enough to te last adjustment we simply perform the + * conversion and add adjustOffsetNs, which is the cached value. Otherwise, + * we need to recompute the offset. This part is a straightforward caching. + * + * The first implementation of the recomputation was a simple + * + * adjustOffsetNs=ns-convert(convert(ns,toTick),toNs); + * lastAdjustTimeNs=ns; + * + * which is straightforward, but it failed when tested with very long + * ns values (100+ years) and low tick frequencies (8MHz or less). + * The reason for this is that the round trip computation first converts + * the given ns value to ticks, which incurs in the error that we want to + * get rid of, and then converts it back to nanoseconds, resulting in a + * value that we call ns2. However, since we are adjusting the + * first conversion to be equal to the second one, the adjust value so + * obtained is good for compensating the error in a range of amplitude + * adjustIntervalNs around ns2, NOT around ns! + * So, if the error grows so large that ns2-ns is greater than + * adjustIntervalNs, the simple round trip written above does not zero + * the error. To solve this, an iterative algorithm is needed. + * + * Somewhere else ns2tick + * in the kernel + * + * tick tick2 --+ + * | ^ | + * | | | + * V | V + * ns -----------+ ns2 + * + * The following diagram explains it further. Soewhere else in the kernel + * a value in ticks was read from the hardware counter, and converted to ns. + * Within ns2tick we don't know what tick is, and if we're doing a round + * trip adjustment we start by back converting ns, resulting in tick2 + * which is different from tick, because the point of all this is that + * the conversion from ns to tick is imprecise. If we then complete the + * round trip and naively compute adjustOffsetNs as the difference from + * ns2 and ns, the resulting adjustment when applied to ns will only + * give us the correct answer, tick, if the difference between ns and ns2 + * is less than adjustIntervalNs. + * + * The algorithm that fixes this works as follows. Before the for loop, + * there's an adjustOffsetNs=0 statement. + * At the first iteration we try to do a round trip around + * ns+adjustOffsetNs, but adjustOffsetNs is zero, so we get the same result + * as the previous round trip described above. As the result of this + * round trip gets us ns2, which as said earlier is the center of the + * validity range for the round trip, we store it in our lastAdjustTimeNs + * variable. Then we compute the adjust offset as the original time point, + * ns+adjustOffsetNs, minus the round trip value ns2. + * Then we check the validity range of our find. The check is whether we + * are in a range which is half the size of our real validity range. This + * is a compromise, as using the full validity range would lower the number + * of iterations of the algorithm that will produce a correct answer, but + * will diminish the value of caching, as ns2tick will be likely called with + * values close to the previous ns one, and if we lastAdjustTimeNs compared + * to ns is at edge of the range, later calls are likely to require a + * readjustment. To utilize the caching range fully, we would ideally want + * to stop when ns==lastAdjustTimeNs, but this will require too many + * iterations and would slow down the algorithm. + * Anyway, if a single iteration isn't enough, we iterate again, using + * the previous adjustOffsetNs as a guess to get make tick2 closer to + * tick this time. The maximum number of iterations that was observed is + * just 3, so the iteration is particularly efficient. + * As you can see, the for loop has a hard limit of 5 iterations. This is + * NOT expected to occur in practice, and has only been done to be 100% + * sure that this algorithm won't turn into an infinite loop when given + * off-design numbers, such as negative numbers. + * The last thing worth mentioning is that a previous implementation omitted + * the adjustOffsetNs=0 before the for loop. As explained, each iteration + * works by using the previous adjustOffsetNs as a guess for the next one, + * and the initializtion of was omitted to use the previous value as a guess + * for the new adjustment. While this was shown to reduce the number of + * iterations in some cases, if ns2tick() was first called with a very large + * value, and then with a very small one, a negative adjustOffsetNs would + * result in underflow and caused wrong results to bbe produced. + * Also, the casts to unsigned are needed because ns+adjustOffsetNs was + * shown to overflow for values of ns close to the maximum number a + * long long can hold and positive adjustOffsetNs, but uns+adjustOffsetNs + * can hold positive numbers far greater than its signed couterpart. + * + * This algorithm was benchmarked on an efm32 Cortex-M4 microcontroller + * running at 48MHz, and this is the runtime: + * using cached value 115 cycles + * readjust 1 iteration 284 cycles + * readjust 2 iterations 438 cycles + */ + + //Negative numbers for ns are not allowed, cast is safe + auto uns=static_cast(ns); + if(uabs(static_cast(uns-lastAdjustTimeNs))>adjustIntervalNs) + { + adjustOffsetNs=0; + for(int i=0;i<5;i++) + { + #ifdef TEST_ALGORITHM + ITERATION; + #endif //TEST_ALGORITHM + lastAdjustTimeNs=convert(convert(uns+adjustOffsetNs,toTick),toNs); + adjustOffsetNs=static_cast((uns+adjustOffsetNs)-lastAdjustTimeNs); + if(uabs(static_cast(uns-lastAdjustTimeNs))<(adjustIntervalNs>>1)) break; + } + #ifdef TEST_ALGORITHM + NL; + #endif //TEST_ALGORITHM + } + return static_cast(convert(uns+adjustOffsetNs,toTick)); +} + +TimeConversion::TimeConversion(unsigned int hz) + : lastAdjustTimeNs(0), adjustOffsetNs(0) +{ + // + // As a first part, compute the initial toNs and toTick coefficients + // + float hzf=static_cast(hz); + toNs=floatToFactor(1e9f/hzf); + toTick=floatToFactor(hzf/1e9f); + + + // + // Then perform the bisection algorithm to optimize toTick offline + // + + /* + * For choosing the time point on which to do the toTick coefficient + * optimization, we need both an as high as possible number, but also + * without fear of overflow. 1<<62 is ~146years, but even if during the + * bisection the number nearly doubles, no overflow occurs. + */ + const unsigned long long longUptimeNs=1ULL<<62; + const unsigned long long longUptimeTick=convert(longUptimeNs,toTick); + /* + * Max correction 25ppm (1/400000=25ppm). This value is a guess based on + * the observed correction values when running with TEST_ALGORITHM, and has + * the advantage that the number of iterations is lower when hz is lower, + * thus being less time consuming on slow CPUs + */ + int aDelta=toTick.fractionalPart()/40000; + int bDelta=-aDelta; + long long aError=computeRoundTripError(longUptimeTick,aDelta); + long long bError=computeRoundTripError(longUptimeTick,bDelta); + if(sign(aError)!=sign(bError)) + { + //Different sign, do a binary search + for(;;) + { + #ifdef TEST_ALGORITHM + ITERATION; + #endif //TEST_ALGORITHM + int cDelta=(aDelta+bDelta)/2; + if(cDelta==aDelta || cDelta==bDelta) break; + long long cError=computeRoundTripError(longUptimeTick,cDelta); + if(sign(aError)==sign(cError)) + { + aDelta=cDelta; + aError=cError; + } else { + bDelta=cDelta; + bError=cError; + } + } + } + int delta=uabs(aError)>=1; + adjustIntervalNs>>=1; + } + adjustIntervalNs>>=1; + #ifdef TEST_ALGORITHM + double adjustInterval=static_cast(adjustIntervalNs)/1e9; + P(adjustInterval); + NL; + #endif //TEST_ALGORITHM + + /* + * This constructor was benchmarked on an efm32 Cortex-M4 microcontroller + * running at 48MHz, and this is the runtime: + * tick freq 32768 2458 cycles + * tick freq 400MHz 4800 cycles + */ +} + +long long TimeConversion::computeRoundTripError(unsigned long long tick, + int delta) const +{ + auto adjustedToTick=toTick+delta; + unsigned long long ns=convert(tick,toNs); + unsigned long long roundTrip=convert(ns,adjustedToTick); + return static_cast(tick-roundTrip); +} + +TimeConversionFactor TimeConversion::floatToFactor(float x) +{ + const float twoPower32=4294967296.f; //2^32 as a float + unsigned int i=x; + unsigned int f=(x-i)*twoPower32; + return TimeConversionFactor(i,f); +} + +} //namespace miosix + +//Testsuite for multiplication algorithm and factor computation. Compile with: +// g++ -std=c++11 -O2 -DTEST_ALGORITHM -o test timeconversion.cpp; ./test +#ifdef TEST_ALGORITHM + +using namespace std; +using namespace miosix; + +void printRoundTripError(TimeConversion& tc, long long tick) +{ + long long roundTripError=tick-tc.ns2tick(tc.tick2ns(tick)); + P(roundTripError); + NL; +} + +long double coefficient(unsigned int bi, unsigned int bd) +{ + //Recompute rounded value using those coefficients. The aim of this test + //is to assess the multiplication algorithm, so we use integer and + //fractional part back-converted to a long double that contains the exact + //same value as the fixed point number + const long double twoPower32=4294967296.; //2^32 as a long double + return static_cast(bd)/twoPower32+static_cast(bi); +} + +void test(double b, unsigned int bi, unsigned int bd, int iterations) +{ + long double br=coefficient(bi,bd); + P(bi); + P(bd); + //This is the error of the 32.32 representation and factor computation + double errorPPM=(b-br)/b*1e6; + P(errorPPM); + + srand(0); + for(int i=0;i(aHi)<<32 + | static_cast(aLo); + unsigned long long result=mul64x32d32(a,bi,bd); + //Our reference is a long double multiplication + unsigned long long reference=static_cast(a)*br; + assert(uabs(result-reference)<2); + } + NL; +} + +void testns2tick(TimeConversion& tc, int iterations) +{ + //First, we get the tick value that results in the maximum ns value + //that fits in a long long + unsigned int bi,bd; + bi=tc.getTick2nsConversion().integerPart(); + bd=tc.getTick2nsConversion().fractionalPart(); + long double toNs=coefficient(bi,bd); + long long maxTick=numeric_limits::max()/toNs; + //Care about rounding + while(tc.tick2ns(maxTick)<0) maxTick--; + print=false; + srand(0); + + //Fully random test + for(int i=0;i(rand() & 0x7fffffff)<<32 + | static_cast(rand()); + a=a % maxTick; + assert(uabs(tc.ns2tick(tc.tick2ns(a))-a)<2); + } + //Large and small test, was proven to check some corner cases + for(int i=0;i(rand() & 0x7fffffff)<<32 + | static_cast(rand()); + a=a % maxTick; + assert(uabs(tc.ns2tick(tc.tick2ns(a))-a)<2); + + long long b=static_cast(rand()); + b=b % maxTick; + assert(uabs(tc.ns2tick(tc.tick2ns(b))-b)<2); + } + //Largest and small test + for(int i=0;i(rand()); + b=b % maxTick; + assert(uabs(tc.ns2tick(tc.tick2ns(b))-b)<2); + } + + print=true; +} + +int main() +{ + vector freqs={10000, 32768, 100000, 1e6, 8e6, 24e6, 48e6, 72e6, + 84e6, 120e6, 168e6, 180e6, 400e6}; + for(double d : freqs) + { + cout<<"==== "<(tc.getAdjustInterval())/1e9*d*0.49; + printRoundTripError(tc,tick-delta); + printRoundTripError(tc,tick+delta); + + b=1e9/d; + bi=tc.getTick2nsConversion().integerPart(); + bd=tc.getTick2nsConversion().fractionalPart(); + test(b,bi,bd,1000000); + + b=d/1e9; + bi=tc.getNs2tickConversion().integerPart(); + bd=tc.getNs2tickConversion().fractionalPart(); + test(b,bi,bd,1000000); + + testns2tick(tc,1000000); + cout< * + ***************************************************************************/ + +#ifndef TIMECONVERSION_H +#define TIMECONVERSION_H + +namespace miosix { + +/** + * Multiplication between a 64 bit integer and a 32.32 fixed point number, + * + * The caller must guarantee that the result of the multiplication fits in + * 64 bits. Otherwise the behaviour is unspecified. + * + * \param a the 64 bit integer number. + * \param bi the 32 bit integer part of the fixed point number + * \param bf the 32 bit fractional part of the fixed point number + * \return the result of the multiplication. The fractional part is discarded. + */ +unsigned long long mul64x32d32(unsigned long long a, + unsigned int bi, unsigned int bf); + +/** + * This class holds a 32.32 fixed point number used for time conversion + */ +class TimeConversionFactor +{ +public: + /** + * Default constructor. Leaves the factors unintialized. + */ + TimeConversionFactor() {} + + /** + * Constructor + * \param i integer part + * \param f fractional part + */ + TimeConversionFactor(unsigned int i, unsigned int f) : i(i), f(f) {} + + /** + * \return the integer part of the fixed point number + */ + inline unsigned int integerPart() const { return i; } + + /** + * \return the fractional part of the fixed point number + */ + inline unsigned int fractionalPart() const { return f; } + + /** + * \param x value to add to the fractional part + * \return a TimeConversionFactor with the same integer part and the + * fractional part corrected by delta + */ + TimeConversionFactor operator+(int delta) const + { + return TimeConversionFactor(i,f+delta); + } + +private: + unsigned int i; + unsigned int f; +}; + +/** + * Instances of this class can be used by timer drivers to convert from ticks + * in the timer resolution to nanoseconds and back. + */ +class TimeConversion +{ +public: + /** + * Constructor + * Set the conversion factors based on the tick frequency. + * \param hz tick frequency in Hz. The range of timer frequencies that are + * supported is 10KHz to 1GHz. The algorithms in this class may not work + * outside this range + */ + TimeConversion(unsigned int hz); + + /** + * \param tick time point in timer ticks + * \return the equivalent time point in the nanosecond timescale + */ + inline long long tick2ns(long long tick) const + { + //Negative numbers for tick are not allowed, cast is safe + auto utick=static_cast(tick); + return static_cast(convert(utick,toNs)); + } + + /** + * \param ns time point in nanoseconds + * \return the equivalent time point in the timer tick timescale + * + * As this function may modify some class variables as part of the + * internal online adjustment process, it is not reentrant. The caller + * is responsible to prevent concurrent calls + */ + long long ns2tick(long long ns); + + /** + * \return the conversion factor from ticks to ns + */ + inline TimeConversionFactor getTick2nsConversion() const { return toNs; } + + /** + * \return the conversion factor from ns to tick + */ + inline TimeConversionFactor getNs2tickConversion() const { return toTick; } + + /** + * \return the time interval in ns from the last online round trip + * adjustment for ns2tick() where the adjust offset is cached. + * This should not matter to you unless you are working on the inner + * details of the round trip adjustment code, otherwise you can safely + * ignore this value + */ + unsigned long long getAdjustInterval() const { return adjustIntervalNs; } + + /** + * \return the cached online round trip adjust offset in ns for ns2tick(). + * This should not matter to you unless you are working on the inner + * details of the round trip adjustment code, otherwise you can safely + * ignore this value + */ + long long getAdjustOffset() const { return adjustOffsetNs; } + +private: + + /** + * Compute the error in ticks of an unadjusted conversion from tick to ns + * and back (a "round trip"), when the toTick conversion coefficient + * is adjusted by delta and at the given time point. + * This function is used internally to compute adjust coefficients. + * \param tick time point in ticks on which to do the round trip + * \param delta signed value to add the the fractional part of toTick + * to obtain a temporary coefficient on which the round trip error is + * computed + * \return the round trip error in ticks + */ + long long __attribute__((noinline)) + computeRoundTripError(unsigned long long tick, int delta) const; + + /** + * \param x time point to convert + * \return the converted time point + */ + static inline unsigned long long convert(unsigned long long x, + TimeConversionFactor tcf) + { + return mul64x32d32(x,tcf.integerPart(),tcf.fractionalPart()); + } + + /** + * \param float a floar number + * \return the number in 32.32 fixed point format + */ + static TimeConversionFactor __attribute__((noinline)) floatToFactor(float x); + + TimeConversionFactor toNs, toTick; + unsigned long long adjustIntervalNs, lastAdjustTimeNs; + long long adjustOffsetNs; +}; + +} //namespace miosix + +#endif //TIMECONVERSION_H diff --git a/lib/miosix-kernel/miosix/miosix.h b/lib/miosix-kernel/miosix/miosix.h new file mode 100644 index 00000000..fd096348 --- /dev/null +++ b/lib/miosix-kernel/miosix/miosix.h @@ -0,0 +1,19 @@ + +//Common #include are grouped here for ease of use + +#ifndef MIOSIX_H +#define MIOSIX_H + +/* Hardware */ +#include "interfaces/arch_registers.h" +#include "interfaces/bsp.h" +/* Miosix kernel */ +#include "kernel/kernel.h" +#include "kernel/sync.h" +#include "kernel/queue.h" +/* Utilities */ +#include "util/util.h" +/* Settings */ +#include "config/miosix_settings.h" + +#endif //MIOSIX_H diff --git a/lib/miosix-kernel/miosix/stdlib_integration/libc_integration.cpp b/lib/miosix-kernel/miosix/stdlib_integration/libc_integration.cpp new file mode 100644 index 00000000..46b00c24 --- /dev/null +++ b/lib/miosix-kernel/miosix/stdlib_integration/libc_integration.cpp @@ -0,0 +1,1141 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 * + * by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "libc_integration.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//// Settings +#include "config/miosix_settings.h" +//// Filesystem +#include "filesystem/file_access.h" +//// Console +#include "kernel/logging.h" +//// kernel interface +#include "kernel/kernel.h" +#include "interfaces/bsp.h" +#include "interfaces/delays.h" +#include "board_settings.h" + +using namespace std; + +namespace miosix { + +// This holds the max heap usage since the program started. +// It is written by _sbrk_r and read by getMaxHeap() +static unsigned int maxHeapEnd=0; + +unsigned int getMaxHeap() +{ + //If getMaxHeap() is called before the first _sbrk_r() maxHeapEnd is zero. + extern char _end asm("_end"); //defined in the linker script + if(maxHeapEnd==0) return reinterpret_cast(&_end); + return maxHeapEnd; +} + +/** + * \return the global C reentrancy structure + */ +static struct _reent *kernelNotStartedGetReent() { return _GLOBAL_REENT; } + +/** + * Pointer to a function that retrieves the correct reentrancy structure. + * When the C reentrancy structure is requested before the kernel is started, + * the default reentrancy structure shall be returned, while after the kernel + * is started, the per-thread reentrancy structure needs to be returned to + * avoid race conditions between threads. + * The function pointer is needed to switch between the two behaviors as the + * per-thread code would cause a circular dependency if called before the + * kernel is started (getCurrentThread needs to allocate a thread with malloc + * if called before the kernel istarted, and malloc needs the reentrancy + * structure). + */ +static struct _reent *(*getReent)()=kernelNotStartedGetReent; + +void setCReentrancyCallback(struct _reent *(*callback)()) { getReent=callback; } + +} //namespace miosix + +#ifdef __cplusplus +extern "C" { +#endif + +// +// C atexit support, for thread safety and code size optimizations +// =============================================================== + +// Prior to Miosix 1.58 atexit was effectively unimplemented, but its partial +// support in newlib used ~384bytes of RAM. Within the kernel it will always +// be unimplemented, so newlib has been patched not to waste RAM. +// The support library for Miosix processes will instead implement those stubs +// so as to support atexit in processes, as in that case it makes sense. + +/** + * Function called by atexit(), on_exit() and __cxa_atexit() to register + * C functions/C++ destructors to be run at program termintion. + * It is called in this way: + * atexit(): __register_exitproc(__et_atexit, fn, 0, 0) + * on_exit(): __register_exitproc(__et_onexit, fn, arg, 0) + * __cxa_atexit(): __register_exitproc(__et_cxa, fn, arg, d) + * \param type to understand if the function was called by atexit, on_exit, ... + * \param fn pointer to function to be called + * \param arg 0 in case of atexit, function argument in case of on_exit, + * "this" parameter for C++ destructors registered with __cxa_atexit + * \param d __dso_handle used to selectively call C++ destructors of a shared + * library loaded dynamically, unused since Miosix does not support shared libs + * \return 0 on success + */ +int __register_exitproc(int type, void (*fn)(void), void *arg, void *d) +{ + (void) type; + (void) fn; + (void) arg; + (void) d; + + return 0; +} + +/** + * Called by exit() to call functions registered through atexit() + * \param code the exit code, for example with exit(1), code==1 + * \param d __dso_handle, see __register_exitproc + */ +void __call_exitprocs(int code, void *d) +{ + (void) code; + (void) d; +} + +/** + * \internal + * Required by C++ standard library. + * See http://lists.debian.org/debian-gcc/2003/07/msg00057.html + */ +void *__dso_handle=(void*) &__dso_handle; + + + + +// +// C/C++ system calls, to support malloc, printf, fopen, etc. +// ========================================================== + +/** + * \internal + * _exit, restarts the system + */ +void _exit(int n) +{ + (void) n; + + miosix::reboot(); + //Never reach here + for(;;) ; //Required to avoid a warning about noreturn functions +} + +/** + * \internal + * _sbrk_r, allocates memory dynamically + */ +void *_sbrk_r(struct _reent *ptr, ptrdiff_t incr) +{ + (void) ptr; + + //This is the absolute start of the heap + extern char _end asm("_end"); //defined in the linker script + //This is the absolute end of the heap + extern char _heap_end asm("_heap_end"); //defined in the linker script + //This holds the current end of the heap (static) + static char *curHeapEnd=NULL; + //This holds the previous end of the heap + char *prevHeapEnd; + + //Check if it's first time called + if(curHeapEnd==NULL) curHeapEnd=&_end; + + prevHeapEnd=curHeapEnd; + if((curHeapEnd+incr)>&_heap_end) + { + //bad, heap overflow + #ifdef __NO_EXCEPTIONS + // When exceptions are disabled operator new would return 0, which would + // cause undefined behaviour. So when exceptions are disabled, a heap + // overflow causes a reboot. + errorLog("\n***Heap overflow\n"); + _exit(1); + #else //__NO_EXCEPTIONS + return reinterpret_cast(-1); + #endif //__NO_EXCEPTIONS + } + curHeapEnd+=incr; + + if(reinterpret_cast(curHeapEnd) > miosix::maxHeapEnd) + miosix::maxHeapEnd=reinterpret_cast(curHeapEnd); + + return reinterpret_cast(prevHeapEnd); +} + +void *sbrk(ptrdiff_t incr) +{ + return _sbrk_r(miosix::getReent(),incr); +} + +/** + * \internal + * __malloc_lock, called by malloc to ensure no context switch happens during + * memory allocation (the heap is global and shared between the threads, so + * memory allocation should not be interrupted by a context switch) + * + * WARNING: + * pauseKernel() does not stop interrupts, so interrupts may occur + * during memory allocation. So NEVER use malloc inside an interrupt! + * Also beware that some newlib functions, like printf, iprintf... + * do call malloc, so you must not use them inside an interrupt. + */ +void __malloc_lock() +{ + miosix::pauseKernel(); +} + +/** + * \internal + * __malloc_unlock, called by malloc after performing operations on the heap + */ +void __malloc_unlock() +{ + miosix::restartKernel(); +} + +/** + * \internal + * __getreent(), return the reentrancy structure of the current thread. + * Used by newlib to make the C standard library thread safe + */ +struct _reent *__getreent() +{ + return miosix::getReent(); +} + + + + +/** + * \internal + * _open_r, open a file + */ +int _open_r(struct _reent *ptr, const char *name, int flags, int mode) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().open(name,flags,mode); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) name; + (void) flags; + (void) mode; + + ptr->_errno=ENFILE; + return -1; + #endif //WITH_FILESYSTEM +} + +int open(const char *name, int flags, ...) +{ + int mode=0; + if(flags & O_CREAT) + { + va_list arg; + va_start(arg,flags); + mode=va_arg(arg,int); + va_end(arg); + } + return _open_r(miosix::getReent(),name,flags,mode); +} + +/** + * \internal + * _close_r, close a file + */ +int _close_r(struct _reent *ptr, int fd) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().close(fd); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) fd; + ptr->_errno=EBADF; + return -1; + #endif //WITH_FILESYSTEM +} + +int close(int fd) +{ + return _close_r(miosix::getReent(),fd); +} + +int write(int fd, const void *buf, size_t cnt) +{ + return _write_r(miosix::getReent(),fd,buf,cnt); +} + + +int read(int fd, void *buf, size_t cnt) +{ + return _read_r(miosix::getReent(),fd,buf,cnt); +} + +/** + * \internal + * _lseek_r, move file pointer + */ +off_t _lseek_r(struct _reent *ptr, int fd, off_t pos, int whence) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + off_t result=miosix::getFileDescriptorTable().lseek(fd,pos,whence); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) fd; + (void) pos; + (void) whence; + ptr->_errno=EBADF; + return -1; + #endif //WITH_FILESYSTEM +} + +off_t lseek(int fd, off_t pos, int whence) +{ + return _lseek_r(miosix::getReent(),fd,pos,whence); +} + +/** + * \internal + * _fstat_r, return file info + */ +int _fstat_r(struct _reent *ptr, int fd, struct stat *pstat) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().fstat(fd,pstat); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + switch(fd) + { + case STDIN_FILENO: + case STDOUT_FILENO: + case STDERR_FILENO: + memset(pstat,0,sizeof(struct stat)); + pstat->st_mode=S_IFCHR;//Character device + pstat->st_blksize=0; //Defualt file buffer equals to BUFSIZ + return 0; + default: + ptr->_errno=EBADF; + return -1; + } + #endif //WITH_FILESYSTEM +} + +int fstat(int fd, struct stat *pstat) +{ + return _fstat_r(miosix::getReent(),fd,pstat); +} + +/** + * \internal + * _stat_r, collect data about a file + */ +int _stat_r(struct _reent *ptr, const char *file, struct stat *pstat) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().stat(file,pstat); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) file; + (void) pstat; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int stat(const char *file, struct stat *pstat) +{ + return _stat_r(miosix::getReent(),file,pstat); +} + +/** + * \internal + * isatty, returns 1 if fd is associated with a terminal + */ +int _isatty_r(struct _reent *ptr, int fd) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().isatty(fd); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) ptr; + switch(fd) + { + case STDIN_FILENO: + case STDOUT_FILENO: + case STDERR_FILENO: + return 1; + default: + return 0; + } + #endif //WITH_FILESYSTEM +} + +int isatty(int fd) +{ + return _isatty_r(miosix::getReent(),fd); +} + +/** + * \internal + * _fntl_r, perform operations on a file descriptor + */ +int _fcntl_r(struct _reent *ptr, int fd, int cmd, int opt) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().fcntl(fd,cmd,opt); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) fd; + (void) cmd; + (void) opt; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int fcntl(int fd, int cmd, ...) +{ + va_list arg; + int result; + struct _reent *r=miosix::getReent(); + switch(cmd) + { + case F_DUPFD: + case F_SETFD: + case F_SETFL: + va_start(arg,cmd); + result=_fcntl_r(r,fd,cmd,va_arg(arg,int)); + va_end(arg); + default: + result=_fcntl_r(r,fd,cmd,0); + } + return result; +} + +/** + * \internal + * _ioctl_r, perform operations on a file descriptor + */ +int _ioctl_r(struct _reent *ptr, int fd, int cmd, void *arg) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().ioctl(fd,cmd,arg); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + if(fd==STDIN_FILENO || fd==STDOUT_FILENO || fd==STDERR_FILENO) + { + int result=miosix::DefaultConsole::instance().getTerminal()->ioctl(cmd,arg); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + } else { + ptr->_errno=ENOENT; + return -1; + } + #endif //WITH_FILESYSTEM +} + +int ioctl(int fd, int cmd, void *arg) +{ + return _ioctl_r(miosix::getReent(),fd,cmd,arg); +} + +/** + * \internal + * _getcwd_r, return current directory + */ +char *_getcwd_r(struct _reent *ptr, char *buf, size_t size) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().getcwd(buf,size); + if(result>=0) return buf; + ptr->_errno=-result; + return NULL; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return NULL; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) buf; + (void) size; + ptr->_errno=ENOENT; + return NULL; + #endif //WITH_FILESYSTEM +} + +char *getcwd(char *buf, size_t size) +{ + return _getcwd_r(miosix::getReent(),buf,size); +} + +/** + * \internal + * _chdir_r, change current directory + */ +int _chdir_r(struct _reent *ptr, const char *path) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().chdir(path); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) path; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int chdir(const char *path) +{ + return _chdir_r(miosix::getReent(),path); +} + +/** + * \internal + * _mkdir_r, create a directory + */ +int _mkdir_r(struct _reent *ptr, const char *path, int mode) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().mkdir(path,mode); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) path; + (void) mode; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int mkdir(const char *path, mode_t mode) +{ + return _mkdir_r(miosix::getReent(),path,mode); +} + +/** + * \internal + * _rmdir_r, remove a directory if empty + */ +int _rmdir_r(struct _reent *ptr, const char *path) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().rmdir(path); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) path; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int rmdir(const char *path) +{ + return _rmdir_r(miosix::getReent(),path); +} + +/** + * \internal + * _link_r: create hardlinks + */ +int _link_r(struct _reent *ptr, const char *f_old, const char *f_new) +{ + (void) f_old; + (void) f_new; + ptr->_errno=ENOENT; //Unimplemented at the moment + return -1; +} + +int link(const char *f_old, const char *f_new) +{ + return _link_r(miosix::getReent(),f_old,f_new); +} + +/** + * \internal + * _unlink_r, remove a file + */ +int _unlink_r(struct _reent *ptr, const char *file) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().unlink(file); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) file; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int unlink(const char *file) +{ + return _unlink_r(miosix::getReent(),file); +} + +/** + * \internal + * _rename_r, rename a file or directory + */ +int _rename_r(struct _reent *ptr, const char *f_old, const char *f_new) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().rename(f_old,f_new); + if(result>=0) return result; + ptr->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + ptr->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) f_old; + (void) f_new; + ptr->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + +int rename(const char *f_old, const char *f_new) +{ + return _rename_r(miosix::getReent(),f_old,f_new); +} + +/** + * \internal + * getdents, allows to list the content of a directory + */ +int getdents(unsigned int fd, struct dirent *dirp, unsigned int count) +{ + #ifdef WITH_FILESYSTEM + + #ifndef __NO_EXCEPTIONS + try { + #endif //__NO_EXCEPTIONS + int result=miosix::getFileDescriptorTable().getdents(fd,dirp,count); + if(result>=0) return result; + miosix::getReent()->_errno=-result; + return -1; + #ifndef __NO_EXCEPTIONS + } catch(exception& e) { + miosix::getReent()->_errno=ENOMEM; + return -1; + } + #endif //__NO_EXCEPTIONS + + #else //WITH_FILESYSTEM + (void) fd; + (void) dirp; + (void) count; + miosix::getReent()->_errno=ENOENT; + return -1; + #endif //WITH_FILESYSTEM +} + + + + +/* + * Time API in Miosix + * ================== + * + * CORE + * - clock_gettime + * - clock_nanosleep + * - clock_settime + * - clock_getres + * + * DERIVED, preferred + * - C++11 chrono system|steady_clock -> clock_gettime + * - C++11 sleep_for|until -> clock_nanosleep + * + * DERIVED, for compatibility (replace with core functions when possible) + * - sleep|usleep -> nanosleep -> clock_nanosleep + * - clock -> times -> clock_gettime + * - time -> gettimeofday -> clock_gettime + * + * UNSUPPORTED + * - timer_create -> ? + */ + +#ifndef _MIOSIX_GCC_PATCH_MAJOR //Before GCC 9.2.0 +#define CLOCK_MONOTONIC 4 +#endif + +/// Conversion factor from ticks to nanoseconds +/// TICK_FREQ in Miosix is either 1000 or (on older chips) 200, so a simple +/// multiplication/division factor does not cause rounding errors +static constexpr long tickNsFactor=1000000000/miosix::TICK_FREQ; + +/** + * Convert from timespec to the Miosix representation of time + * \param tp input timespec, must not be nullptr and be a valid pointer + * \return Miosix ticks + */ +inline long long timespec2ll(const struct timespec *tp) +{ + //NOTE: the cast is required to prevent overflow with older versions + //of the Miosix compiler where tv_sec is int and not long long + return static_cast(tp->tv_sec)*miosix::TICK_FREQ + + tp->tv_nsec/tickNsFactor; +} + +/** + * Convert from he Miosix representation of time to a timespec + * \param tick input Miosix ticks + * \param tp output timespec, must not be nullptr and be a valid pointer + */ +inline void ll2timespec(long long tick, struct timespec *tp) +{ + #ifdef __ARM_EABI__ + // Despite there being a single intrinsic, __aeabi_ldivmod, that computes + // both the result of the / and % operator, GCC 9.2.0 isn't smart enough and + // calls the intrinsic twice. This asm implementation saves ~115 cycles + // by calling it once. Sadly, I had to use asm as the calling conventions + // of the intrinsic appear to be nonstandard. + // NOTE: actually a and b, by being 64 bit numbers, occupy register pairs + register long long a asm("r0") = tick; + register long long b asm("r2") = miosix::TICK_FREQ; + // NOTE: clobbering lr to mark function not leaf due to the bl + asm volatile("bl __aeabi_ldivmod" : "+r"(a), "+r"(b) :: "lr"); + tp->tv_sec = a; + tp->tv_nsec = static_cast(b) * tickNsFactor; + #else //__ARM_EABI__ + tp->tv_sec = tick / miosix::TICK_FREQ; + tp->tv_nsec = static_cast(tick % miosix::TICK_FREQ) * tickNsFactor; + #endif //__ARM_EABI__ +} + +int clock_gettime(clockid_t clock_id, struct timespec *tp) +{ + (void) clock_id; + (void) tp; + + if(tp==nullptr) return -1; + //TODO: support CLOCK_REALTIME + ll2timespec(miosix::getTick(),tp); + return 0; +} + +int clock_settime(clockid_t clock_id, const struct timespec *tp) +{ + (void) clock_id; + (void) tp; + + //TODO: support CLOCK_REALTIME + return -1; +} + +int clock_getres(clockid_t clock_id, struct timespec *res) +{ + (void) clock_id; + + if(res==nullptr) return -1; + res->tv_sec=0; + res->tv_nsec=tickNsFactor; + return 0; +} + +int clock_nanosleep(clockid_t clock_id, int flags, + const struct timespec *req, struct timespec *rem) +{ + (void) clock_id; + (void) rem; + + if(req==nullptr) return -1; + //TODO: support CLOCK_REALTIME + long long timeTick=timespec2ll(req); + if(flags!=TIMER_ABSTIME) timeTick+=miosix::getTick(); + miosix::Thread::sleepUntil(timeTick); + return 0; +} + +/** + * \internal + * _times_r, return elapsed time + */ +clock_t _times_r(struct _reent *ptr, struct tms *tim) +{ + (void) ptr; + + struct timespec tp; + //No CLOCK_PROCESS_CPUTIME_ID support, use CLOCK_MONOTONIC + if(clock_gettime(CLOCK_MONOTONIC,&tp)) return static_cast(-1); + constexpr int divFactor=1000000000/CLOCKS_PER_SEC; + clock_t utime=tp.tv_sec*CLOCKS_PER_SEC + tp.tv_nsec/divFactor; + + //Actually, we should return tim.utime or -1 on failure, but clock_t is + //unsigned, so if we return tim.utime and someone calls _times_r in an + //unlucky moment where tim.utime is 0xffffffff it would be interpreted as -1 + //IMHO, the specifications are wrong since returning an unsigned leaves + //no value left to return in case of errors. Thus 0 is returned if a valid + //pointer is passed, and tim.utime if the pointer is null + if(tim==nullptr) return utime; + tim->tms_utime=utime; + tim->tms_stime=0; + tim->tms_cutime=0; + tim->tms_cstime=0; + return 0; +} + +clock_t times(struct tms *tim) +{ + return _times_r(miosix::getReent(),tim); +} + +int _gettimeofday_r(struct _reent *ptr, struct timeval *tv, void *tz) +{ + (void) ptr; + + if(tv==nullptr || tz!=nullptr) return -1; + struct timespec tp; + if(clock_gettime(CLOCK_REALTIME,&tp)) return -1; + tv->tv_sec=tp.tv_sec; + tv->tv_usec=tp.tv_nsec/1000; + return 0; +} + +int gettimeofday(struct timeval *tv, void *tz) +{ + return _gettimeofday_r(miosix::getReent(),tv,tz); +} + + +int nanosleep(const struct timespec *req, struct timespec *rem) +{ + return clock_nanosleep(CLOCK_MONOTONIC,0,req,rem); +} + + + + +/** + * \internal + * it looks like abort() calls _kill instead of exit, this implementation + * calls _exit() so that calling abort() really terminates the program + */ +int _kill_r(struct _reent* ptr, int pid, int sig) +{ + (void) ptr; + (void) sig; + + if(pid==0) _exit(1); //pid=1 means the only running process + else return -1; +} + +int kill(int pid, int sig) +{ + return _kill_r(miosix::getReent(),pid,sig); +} + +/** + * \internal + * _getpid_r, there is only one process in Miosix (but multiple threads) + */ +int _getpid_r(struct _reent* ptr) +{ + (void) ptr; + + return 0; +} + +/** + * \internal + * getpid, there is only one process in Miosix (but multiple threads) + */ +int getpid() +{ + return _getpid_r(miosix::getReent()); +} + +/** + * \internal + * _wait_r, unimpemented because processes are not supported in Miosix + */ +int _wait_r(struct _reent *ptr, int *status) +{ + (void) ptr; + (void) status; + + return -1; +} + +int wait(int *status) +{ + return _wait_r(miosix::getReent(),status); +} + +/** + * \internal + * _execve_r, unimpemented because processes are not supported in Miosix + */ +int _execve_r(struct _reent *ptr, const char *path, char *const argv[], + char *const env[]) +{ + (void) ptr; + (void) path; + (void) argv; + (void) env; + + return -1; +} + +int execve(const char *path, char *const argv[], char *const env[]) +{ + return _execve_r(miosix::getReent(),path,argv,env); +} + +/** + * \internal + * _forkexecve_r, reserved for future use + */ +pid_t _forkexecve_r(struct _reent *ptr, const char *path, char *const argv[], + char *const env[]) +{ + (void) ptr; + (void) path; + (void) argv; + (void) env; + + return -1; +} + +pid_t forkexecve(const char *path, char *const argv[], char *const env[]) +{ + return _forkexecve_r(miosix::getReent(),path,argv,env); +} + +#ifdef __cplusplus +} +#endif + + + + +// +// Check that newlib has been configured correctly +// =============================================== + +#ifndef _REENT_SMALL +#error "_REENT_SMALL not defined" +#endif //_REENT_SMALL + +#ifndef _POSIX_THREADS +#error "_POSIX_THREADS not defined" +#endif //_POSIX_THREADS + +#ifndef __DYNAMIC_REENT__ +#error "__DYNAMIC_REENT__ not defined" +#endif diff --git a/lib/miosix-kernel/miosix/stdlib_integration/libc_integration.h b/lib/miosix-kernel/miosix/stdlib_integration/libc_integration.h new file mode 100644 index 00000000..abff8245 --- /dev/null +++ b/lib/miosix-kernel/miosix/stdlib_integration/libc_integration.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef LIBC_INTEGRATION_H +#define LIBC_INTEGRATION_H + +#include +#include +#include + +#ifndef COMPILING_MIOSIX +#error "This is header is private, it can't be used outside Miosix itself." +#error "If your code depends on a private header, it IS broken." +#endif //COMPILING_MIOSIX + +namespace miosix { + +/** + * \internal + * \return the heap high watermark. Note that the returned value is a memory + * address and not a size in bytes. This is just an implementation detail, what + * you'd want to call is most likely MemoryProfiling::getAbsoluteFreeHeap(). + */ +unsigned int getMaxHeap(); + +/** + * \internal + * Used by the kernel during the boot process to switch the C standard library + * from using a single global reentrancy structure to using per-thread + * reentrancy structures + * \param callback a function that return the per-thread reentrancy structure + */ +void setCReentrancyCallback(struct _reent *(*callback)()); + +} //namespace miosix + +#endif //LIBC_INTEGRATION_H diff --git a/lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.cpp b/lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.cpp new file mode 100644 index 00000000..35fcb4fb --- /dev/null +++ b/lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.cpp @@ -0,0 +1,370 @@ +/*************************************************************************** + * Copyright (C) 2008-2019 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "libstdcpp_integration.h" +#include +#include +#include +#if _MIOSIX_GCC_PATCH_MAJOR > 2 +#include +#endif +//// Settings +#include "config/miosix_settings.h" +//// Console +#include "kernel/logging.h" +//// kernel interface +#include "kernel/kernel.h" + +using namespace std; + +// +// C++ exception support +// ===================== + +#if __cplusplus >= 201703L +#warning: TODO: Override new with alignment (libsupc++/new_opa.cc, new_opv.cc, ... +#warning: TODO: FIX __gthread_key_t in libstdc++/include/std/memory_resource +#endif + +#ifdef __NO_EXCEPTIONS +/* + * If not using exceptions, ovverride the default new, delete with + * an implementation that does not throw, to minimze code size + */ +void *operator new(size_t size) noexcept +{ + return malloc(size); +} + +void *operator new(size_t size, const std::nothrow_t&) noexcept +{ + return malloc(size); +} + +void *operator new[](size_t size) noexcept +{ + return malloc(size); +} + +void *operator new[](size_t size, const std::nothrow_t&) noexcept +{ + return malloc(size); +} + +void operator delete(void *p) noexcept +{ + free(p); +} + +void operator delete[](void *p) noexcept +{ + free(p); +} + + +void operator delete(void *p, std::size_t size) noexcept +{ + (void) size; + + free(p); +} + +void operator delete[](void *p, std::size_t size) noexcept +{ + (void) size; + + free(p); +} + +/** + * \internal + * The default version of these functions provided with libstdc++ require + * exception support. This means that a program using pure virtual functions + * incurs in the code size penalty of exception support even when compiling + * without exceptions. By replacing the default implementations with these one + * the problem is fixed. + */ +extern "C" void __cxxabiv1::__cxa_pure_virtual(void) +{ + errorLog("\n***Pure virtual method called\n"); + _exit(1); +} + +extern "C" void __cxxabiv1::__cxa_deleted_virtual(void) +{ + errorLog("\n***Deleted virtual method called\n"); + _exit(1); +} + +#if _MIOSIX_GCC_PATCH_MAJOR > 2 +namespace std { +void terminate() noexcept { _exit(1); } //Since GCC 9.2.0 +void unexpected() noexcept { _exit(1); } //Since GCC 9.2.0 +/* + * This one comes from thread.cc, the need to call the class destructor makes it + * call __cxa_end_cleanup which pulls in exception code. + */ +extern "C" void* execute_native_thread_routine(void* __p) +{ + thread::_State_ptr __t{ static_cast(__p) }; + __t->_M_run(); + return nullptr; +} +} //namespace std +#endif + +/* + * If not using exceptions, ovverride these functions with + * an implementation that does not throw, to minimze code size + */ +namespace std { +void __throw_bad_exception() { _exit(1); } +void __throw_bad_alloc() { _exit(1); } +void __throw_bad_cast() { _exit(1); } +void __throw_bad_typeid() { _exit(1); } +void __throw_logic_error(const char*) { _exit(1); } +void __throw_domain_error(const char*) { _exit(1); } +void __throw_invalid_argument(const char*) { _exit(1); } +void __throw_length_error(const char*) { _exit(1); } +void __throw_out_of_range(const char*) { _exit(1); } +void __throw_out_of_range_fmt(const char*, ...) { exit(1); } //Since GCC 9.2.0 +void __throw_runtime_error(const char*) { _exit(1); } +void __throw_range_error(const char*) { _exit(1); } +void __throw_overflow_error(const char*) { _exit(1); } +void __throw_underflow_error(const char*) { _exit(1); } +#if !defined(_MIOSIX_GCC_PATCH_MAJOR) || _MIOSIX_GCC_PATCH_MAJOR <= 2 +void __throw_ios_failure(const char*) { _exit(1); } //Unused since GCC 9.2.0 +#endif +void __throw_system_error(int) { _exit(1); } +void __throw_future_error(int) { _exit(1); } +void __throw_bad_function_call() { _exit(1); } +} //namespace std + +namespace __cxxabiv1 { +extern "C" void __cxa_throw_bad_array_length() { exit(1); } //Since GCC 9.2.0 +extern "C" void __cxa_bad_cast() { exit(1); } //Since GCC 9.2.0 +extern "C" void __cxa_bad_typeid() { exit(1); } //Since GCC 9.2.0 +extern "C" void __cxa_throw_bad_array_new_length() { exit(1); } //Since GCC 9.2.0 +} //namespace __cxxabiv1 + +#endif //__NO_EXCEPTIONS + +namespace miosix { + +class CppReentrancyAccessor +{ +public: + static __cxxabiv1::__cxa_eh_globals *getEhGlobals() + { + return &miosix::Thread::getCurrentThread()->cppReentrancyData.eh_globals; + } + + #ifndef __ARM_EABI__ + static void *getSjljPtr() + { + return miosix::Thread::getCurrentThread()->cppReentrancyData.sjlj_ptr; + } + + static void setSjljPtr(void *ptr) + { + miosix::Thread::getCurrentThread()->cppReentrancyData.sjlj_ptr=ptr; + } + #endif //__ARM_EABI__ +}; + +} //namespace miosix + +/* + * If exception support enabled, ensure it is thread safe. + * The functions __cxa_get_globals_fast() and __cxa_get_globals() need to + * return a per-thread data structure. Given that thread local storage isn't + * implemented in Miosix, libstdc++ was patched to make these functions syscalls + */ +namespace __cxxabiv1 +{ + +extern "C" __cxa_eh_globals* __cxa_get_globals_fast() +{ + return miosix::CppReentrancyAccessor::getEhGlobals(); +} + +extern "C" __cxa_eh_globals* __cxa_get_globals() +{ + return miosix::CppReentrancyAccessor::getEhGlobals(); +} + +#ifndef __ARM_EABI__ +extern "C" void _Miosix_set_sjlj_ptr(void* ptr) +{ + miosix::CppReentrancyAccessor::setSjljPtr(ptr); +} + +extern "C" void *_Miosix_get_sjlj_ptr() +{ + return miosix::CppReentrancyAccessor::getSjljPtr(); +} +#endif //__ARM_EABI__ + +} //namespace __cxxabiv1 + +namespace __gnu_cxx { + +/** + * \internal + * Replaces the default verbose terminate handler. + * Avoids the inclusion of code to demangle C++ names, which saves code size + * when using exceptions. + */ +void __verbose_terminate_handler() +{ + errorLog("\n***Unhandled exception thrown\n"); + _exit(1); +} + +} //namespace __gnu_cxx + + + + +// +// C++ static constructors support, to achieve thread safety +// ========================================================= + +//This is weird, despite almost everywhere in GCC's documentation it is said +//that __guard is 8 bytes, it is actually only four. +union MiosixGuard +{ + miosix::Thread *owner; + unsigned int flag; +}; + +namespace __cxxabiv1 +{ +/** + * Used to initialize static objects only once, in a threadsafe way + * \param g guard struct + * \return 0 if object already initialized, 1 if this thread has to initialize + * it, or lock if another thread has already started initializing it + */ +extern "C" int __cxa_guard_acquire(__guard *g) +{ + miosix::InterruptDisableLock dLock; + volatile MiosixGuard *guard=reinterpret_cast(g); + for(;;) + { + if(guard->flag==1) return 0; //Object already initialized, good + + if(guard->flag==0) + { + //Object uninitialized, and no other thread trying to initialize it + guard->owner=miosix::Thread::IRQgetCurrentThread(); + + //guard->owner serves the double task of being the thread id of + //the thread initializing the object, and being the flag to signal + //that the object is initialized or not. If bit #0 of guard->owner + //is @ 1 the object is initialized. All this works on the assumption + //that Thread* pointers never have bit #0 @ 1, and this assetion + //checks that this condition really holds + if(guard->flag & 1) miosix::errorHandler(miosix::UNEXPECTED); + return 1; + } + + //If we get here, the object is being initialized by another thread + if(guard->owner==miosix::Thread::IRQgetCurrentThread()) + { + //Wait, the other thread initializing the object is this thread?!? + //We have a recursive initialization error. Not throwing an + //exception to avoid pulling in exceptions even with -fno-exception + IRQerrorLog("Recursive initialization\r\n"); + _exit(1); + } + + { + miosix::InterruptEnableLock eLock(dLock); + miosix::Thread::yield(); //Sort of a spinlock, a "yieldlock"... + } + } +} + +/** + * Called after the thread has successfully initialized the object + * \param g guard struct + */ +extern "C" void __cxa_guard_release(__guard *g) noexcept +{ + miosix::InterruptDisableLock dLock; + MiosixGuard *guard=reinterpret_cast(g); + guard->flag=1; +} + +/** + * Called if an exception was thrown while the object was being initialized + * \param g guard struct + */ +extern "C" void __cxa_guard_abort(__guard *g) noexcept +{ + miosix::InterruptDisableLock dLock; + MiosixGuard *guard=reinterpret_cast(g); + guard->flag=0; +} + +} //namespace __cxxabiv1 + +// +// libatomic support, to provide thread safe atomic operation fallbacks +// ==================================================================== + +// Not using the fast version, as these may be used before the kernel is started + +extern "C" unsigned int libat_quick_lock_n(void *ptr) +{ + (void) ptr; + + miosix::disableInterrupts(); + return 0; +} + +extern "C" void libat_quick_unlock_n(void *ptr, unsigned int token) +{ + (void) ptr; + (void) token; + + miosix::enableInterrupts(); +} + +// These are to implement "heavy" atomic operations, which are not used in +// libstdc++. For now let's keep them disbaled. + +// extern "C" void libat_lock_n(void *ptr, size_t n) +// { +// miosix::pauseKernel(); +// } +// +// extern "C" void libat_unlock_n(void *ptr, size_t n) +// { +// miosix::restartKernel(); +// } diff --git a/lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.h b/lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.h new file mode 100644 index 00000000..df20b064 --- /dev/null +++ b/lib/miosix-kernel/miosix/stdlib_integration/libstdcpp_integration.h @@ -0,0 +1,114 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef LIBSTDCPP_INTEGRATION_H +#define LIBSTDCPP_INTEGRATION_H + +//Can't yet make this header private, as it's included by kernel.h +//#ifndef COMPILING_MIOSIX +//#error "This is header is private, it can't be used outside Miosix itself." +//#error "If your code depends on a private header, it IS broken." +//#endif //COMPILING_MIOSIX + +#include "kernel/error.h" + +namespace __cxxabiv1 +{ +struct __cxa_exception; //A forward declaration of this one is enough + +/* + * This struct was taken from libsupc++/unwind-cxx.h Unfortunately that file + * is not deployed in the gcc installation so we can't just #include it. + * It is required on a per-thread basis to make C++ exceptions thread safe. + */ +struct __cxa_eh_globals +{ + __cxa_exception *caughtExceptions; + unsigned int uncaughtExceptions; + //Should be __ARM_EABI_UNWINDER__ but that's only usable inside gcc + #ifdef __ARM_EABI__ + __cxa_exception* propagatingExceptions; + #endif //__ARM_EABI__ +}; + +} //namespace __cxxabiv1 + +namespace miosix { + +//Forward declaration of a class to hide accessors to CppReentrancyData +class CppReentrancyAccessor; + +/** + * \internal + * This is a wrapper class that contains all per-thread data required to make + * C++ exceptions thread safe, irrespective of the ABI. + */ +class CppReentrancyData +{ +public: + /** + * Constructor, initializes the exception related data to their default + * value + */ + CppReentrancyData() + { + eh_globals.caughtExceptions=0; + eh_globals.uncaughtExceptions=0; + #ifdef __ARM_EABI__ + eh_globals.propagatingExceptions=0; + #else //__ARM_EABI__ + sjlj_ptr=0; + #endif //__ARM_EABI__ + } + + /** + * Destructor, checks that no memory is leaked (should never happen) + */ + ~CppReentrancyData() + { + if(eh_globals.caughtExceptions!=0) errorHandler(UNEXPECTED); + } + +private: + CppReentrancyData(const CppReentrancyData&); + CppReentrancyData& operator=(const CppReentrancyData&); + + /// With the arm eabi unwinder, this is the only data structure required + /// to make C++ exceptions threadsafe. + __cxxabiv1::__cxa_eh_globals eh_globals; + #ifndef __ARM_EABI__ + /// On other no arm architectures, it looks like gcc requires also this + /// pointer to make the unwinder thread safe + void *sjlj_ptr; + #endif //__ARM_EABI__ + + friend class CppReentrancyAccessor; +}; + +} //namespace miosix + +#endif //LIBSTDCPP_INTEGRATION_H diff --git a/lib/miosix-kernel/miosix/util/crc16.cpp b/lib/miosix-kernel/miosix/util/crc16.cpp new file mode 100644 index 00000000..63049cb5 --- /dev/null +++ b/lib/miosix-kernel/miosix/util/crc16.cpp @@ -0,0 +1,44 @@ + +#include "crc16.h" + +namespace miosix { + +/** + * 16 bit ccitt crc calculation + * \param crc the firs time the function is called, pass 0xffff, all the other + * times, pass the previous value. The value returned after the last call is + * the desired crc + * \param data a byte of the message + */ +static inline void crc16Update(unsigned short& crc, unsigned char data) +{ + unsigned short x=((crc>>8) ^ data) & 0xff; + x^=x>>4; + crc=(crc<<8) ^ (x<<12) ^ (x<<5) ^ x; +} + +unsigned short crc16(const void *message, unsigned int length) +{ + const unsigned char *m=reinterpret_cast(message); + unsigned short result=0xffff; + for(unsigned int i=0;i +#include + +using namespace std; + +int main() +{ + unsigned char test[]="Some test string"; + cout<<"0x"< +#include +#include "miosix.h" +#include "interfaces/delays.h" +#include "lcd44780.h" + +namespace miosix { + +Lcd44780::Lcd44780(miosix::GpioPin rs, miosix::GpioPin e, miosix::GpioPin d4, + miosix::GpioPin d5, miosix::GpioPin d6, miosix::GpioPin d7, + int row, int col) : rs(rs), e(e), d4(d4), d5(d5), d6(d6), d7(d7), + row(row), col(col) +{ + rs.mode(Mode::OUTPUT); + e.mode(Mode::OUTPUT); + d4.mode(Mode::OUTPUT); + d5.mode(Mode::OUTPUT); + d6.mode(Mode::OUTPUT); + d7.mode(Mode::OUTPUT); + e.low(); + Thread::sleep(50); //Powerup delay + init(); + clear(); +} + +void Lcd44780::go(int x, int y) +{ + if(x<0 || x>=col || y<0 || y>=row) return; + + // 4x20 is implemented as 2x40. + // TODO Test 4x16 display. + if(y>1) + { + x += col; + } + + comd(0x80 | ((y & 1) ? 0x40 : 0) | x); //Move cursor +} + +int Lcd44780::printf(const char* fmt, ...) +{ + va_list arg; + char line[40]; + va_start(arg,fmt); + int len=vsnprintf(line,sizeof(line),fmt,arg); + va_end(arg); + for(int i=0;i * + ***************************************************************************/ + +#ifndef SOFTWARE_I2C_H +#define SOFTWARE_I2C_H + +#include "interfaces/gpio.h" +#include "interfaces/delays.h" + +namespace miosix { + +/** + * Software I2C class. + * \param SDA SDA gpio pin. Pass a Gpio class + * \param SCL SCL gpio pin. Pass a Gpio class + * \param timeout for clock stretching, in milliseconds + * \param fast false=~100KHz true=~400KHz + */ +template +class SoftwareI2C +{ +public: + /** + * Initializes the SPI software peripheral + */ + static void init(); + + /** + * Send a start condition. + */ + static void sendStart(); + + /** + * Send a repeated start. + */ + static void sendRepeatedStart(); + + /** + * Send a stop condition + */ + static void sendStop(); + + /** + * Send a byte to a device. + * \param data byte to send + * \return true if the device acknowledged the byte + */ + static bool send(unsigned char data); + + /** + * Receive a byte from a device. Always acknowledges back. + * \return the received byte + */ + static unsigned char recvWithAck(); + + /** + * Receive a byte from a device. Never acknowledges back. + * \return the received byte + */ + static unsigned char recvWithNack(); + +private: + SoftwareI2C();//Disallow creating instances, class is used via typedefs + + /** + * Wait if the slave asserts SCL low. This is called clock stretching. + * \return true on success, false on timeout + */ + static bool waitForClockStretching(); +}; + +template +void SoftwareI2C::init() +{ + SDA::high(); + SCL::high(); + SDA::mode(Mode::OPEN_DRAIN); + SCL::mode(Mode::OPEN_DRAIN); +} + +template +void SoftwareI2C::sendStart() +{ + SDA::low(); + delayUs(fast ? 1 : 3); + SCL::low(); + delayUs(fast ? 1 : 3); +} + +template +void SoftwareI2C::sendRepeatedStart() +{ + SDA::high(); + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 1 : 3); + waitForClockStretching(); + sendStart(); +} + +template +void SoftwareI2C::sendStop() +{ + SDA::low(); + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 1 : 3); + waitForClockStretching(); + SDA::high(); + delayUs(fast ? 1 : 3); +} + +template +bool SoftwareI2C::send(unsigned char data) +{ + for(int i=0;i<8;i++) + { + if(data & 0x80) SDA::high(); else SDA::low(); + delayUs(fast ? 1 : 3); + SCL::high(); + data<<=1; + delayUs(fast ? 2 : 5);//Double delay + waitForClockStretching(); + SCL::low(); + delayUs(fast ? 1 : 3); + } + SDA::high(); + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 1 : 3); + bool timeout=waitForClockStretching(); + bool result=(SDA::value()==0); + delayUs(fast ? 1 : 3); + SCL::low(); + delayUs(fast ? 1 : 3); + return result && timeout; +} + +template +unsigned char SoftwareI2C::recvWithAck() +{ + SDA::high(); + unsigned char result=0; + delayUs(fast ? 1 : 3); + for(int i=0;i<8;i++) + { + result<<=1; + + if(SDA::value()) result |= 1; + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 2 : 5);//Double delay + waitForClockStretching(); + SCL::low(); + delayUs(fast ? 1 : 3); + } + SDA::low(); + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 2 : 5);//Double delay + waitForClockStretching(); + SCL::low(); + delayUs(fast ? 1 : 3); + return result; +} + +template +unsigned char SoftwareI2C::recvWithNack() +{ + SDA::high(); + unsigned char result=0; + delayUs(fast ? 1 : 3); + for(int i=0;i<8;i++) + { + result<<=1; + + if(SDA::value()) result |= 1; + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 2 : 5);//Double delay + waitForClockStretching(); + SCL::low(); + delayUs(fast ? 1 : 3); + } + delayUs(fast ? 1 : 3); + SCL::high(); + delayUs(fast ? 2 : 5);//Double delay + waitForClockStretching(); + SCL::low(); + delayUs(fast ? 1 : 3); + return result; +} + +template +bool SoftwareI2C::waitForClockStretching() +{ + if(SCL::value()==1) return true; + for(unsigned int i=0;i * + ***************************************************************************/ + +#ifndef SOFTWARE_SPI_H +#define SOFTWARE_SPI_H + +#include "interfaces/gpio.h" + +namespace miosix { + +/** + * Software implementation of the SPI protocol mode 0 (CPOL=0, CPHA=0 mode) + * \param SI an instance of the Gpio class indicating the SPI input pin + * \param SO an instance of the Gpio class indicating the SPI output pin + * \param SCK an instance of the Gpio class indicating the SPI clock pin + * \param CE an instance of the Gpio class indicating the SPI chip enable pin + * \param numNops number of nops to add to the send loop to slow down SPI clock + */ +template +class SoftwareSPI +{ +public: + /** + * Initialize the SPI interface + */ + static void init() + { + SI::mode(Mode::INPUT); + SO::mode(Mode::OUTPUT); + SCK::mode(Mode::OUTPUT); + CE::mode(Mode::OUTPUT); + CE::high(); + } + + /** + * Send a byte and, since SPI is full duplex, simultaneously receive a byte + * \param data to send + * \return data received + */ + static unsigned char sendRecvChar(unsigned char data); + + /** + * Send an unsigned short and, since SPI is full duplex, simultaneously + * receive an unsigned short + * \param data to send + * \return data received + */ + static unsigned short sendRecvShort(unsigned short data); + + /** + * Send an int and, since SPI is full duplex, simultaneously receive an int + * \param data to send + * \return data received + */ + static unsigned int sendRecvLong(unsigned int data); + + /** + * Pull CE low, indicating transmission start. + */ + static void ceLow() { CE::low(); } + + /** + * Pull CE high, indicating transmission end. + */ + static void ceHigh() { CE::high(); } + +private: + /** + * Loop used to slow down the SPI as needed + */ + static void delayLoop(); +}; + +template +unsigned char SoftwareSPI:: + sendRecvChar(unsigned char data) +{ + unsigned char result=0; + for(int i=0;i<8;i++) + { + if(data & 0x80) SO::high(); else SO::low(); + data<<=1; + delayLoop(); + SCK::high(); + result<<=1; + if(SI::value()) result |= 0x1; + delayLoop(); + SCK::low(); + } + return result; +} + +template +unsigned short SoftwareSPI:: + sendRecvShort(unsigned short data) +{ + unsigned short result=0; + for(int i=0;i<16;i++) + { + if(data & 0x8000) SO::high(); else SO::low(); + data<<=1; + delayLoop(); + SCK::high(); + result<<=1; + if(SI::value()) result |= 0x1; + delayLoop(); + SCK::low(); + } + return result; +} + +template +unsigned int SoftwareSPI:: + sendRecvLong(unsigned int data) +{ + unsigned int result=0; + for(int i=0;i<32;i++) + { + if(data & 0x80000000) SO::high(); else SO::low(); + data<<=1; + delayLoop(); + SCK::high(); + result<<=1; + if(SI::value()) result |= 0x1; + delayLoop(); + SCK::low(); + } + return result; +} + +template +void SoftwareSPI::delayLoop() +{ + for(int j=0;j * + ***************************************************************************/ + +#include "unicode.h" + +using namespace std; + + #define PUT(x) do \ +{ \ + if(length>=dstSize) return make_pair(INSUFFICIENT_SPACE,length); \ + *dst++=x; length++; \ +} while(0) + +namespace miosix { + +pair Unicode::putUtf8(char *dst, char32_t c, int dstSize) +{ + //Reserved space for surrogate pairs in utf16 are invalid code points + if(c>=0xd800 && c<= 0xdfff) return make_pair(INVALID_STRING,0); + //Unicode is limited in the range 0-0x10ffff + if(c>0x10ffff) return make_pair(INVALID_STRING,0); + int length=0; + + if(c<0x80) + { + PUT(c); + return make_pair(OK,length); + } + + if(c<0x800) + { + PUT(c>>6 | 0xc0); + } else if(c<0x10000) { + PUT(c>>12 | 0xe0); + PUT(((c>>6) & 0x3f) | 0x80); + } else { + PUT(c>>18 | 0xf0); + PUT(((c>>12) & 0x3f) | 0x80); + PUT(((c>>6) & 0x3f) | 0x80); + } + PUT((c & 0x3f) | 0x80); + return make_pair(OK,length); +} + +pair Unicode::utf8toutf16(char16_t *dst, int dstSize, + const char *src) +{ + int length=0; + + for(;;) + { + char32_t c=nextUtf8(src); + if(c==0) break; + if(c==invalid) return make_pair(INVALID_STRING,length); + + if(c>0xffff) + { + const char32_t leadOffset=0xd800-(0x10000>>10); + PUT(leadOffset+(c>>10)); + PUT(0xdc00+(c & 0x3ff)); + } else PUT(c); + } + + PUT(0); //Terminate string + return make_pair(OK,length-1); +} + +pair Unicode::utf16toutf8(char *dst, int dstSize, + const char16_t *src) +{ + //Note: explicit cast to be double sure that no sign extension happens + const unsigned short *srcu=reinterpret_cast(src); + int length=0; + + while(char32_t c=*srcu++) + { + //Common case first: ASCII + if(c<0x80) + { + PUT(c); + continue; + } + + //If not ASCII, pass through utf32 + if(c>=0xd800 && c<=0xdbff) + { + char32_t next=*srcu++; + //Unpaired lead surrogate (this includes the case next==0) + if(next<0xdc00 || next>0xdfff) return make_pair(INVALID_STRING,length); + + const char32_t surrogateOffset=0x10000-(0xd800<<10)-0xdc00; + c=(c<<10)+next+surrogateOffset; + } else if(c>=0xdc00 && c<=0xdfff) { + //Unpaired trail surrogate + return make_pair(INVALID_STRING,length); + } + + pair result=putUtf8(dst,c,dstSize-length); + dst+=result.second; + length+=result.second; + if(result.first!=OK) return make_pair(result.first,length); + } + + PUT(0); //Terminate string + return make_pair(OK,length-1); +} + +pair Unicode::validateUtf8(const char* str) +{ + const char *iter=str; + for(;;) + { + char32_t codePoint=nextUtf8(iter); + if(codePoint==0) return make_pair(true,iter-str); + if(codePoint==invalid) return make_pair(false,iter-str); + } +} + +} //namespace miosix + +/* +#include +#include +#include + +using namespace std; +using namespace miosix; + +int main(int argc, char *argv[]) +{ + ifstream in(argv[1]); + in.seekg(0,ios::end); + const int size=in.tellg(); + in.seekg(0,ios::beg); + ofstream out(argv[2]); + if(argv[3][0]=='u') + { + char *c=new char[size+1]; + in.read(c,size); + c[size]='\0'; + char16_t *cc=new char16_t[512]; + pair result=Unicode::utf8toutf16(cc,512,c); + assert(result.first==Unicode::OK); + cout<<"Target string len "< result=Unicode::utf16toutf8(cc,1024,c); + assert(result.first==Unicode::OK); + cout<<"Target string len "< * + ***************************************************************************/ + +#include +#include + +#ifndef UNICODE_H +#define UNICODE_H + +#if __cplusplus <= 199711L +//These are builtin types in C++11, add them if compiling in C++03 mode +typedef uint16_t char16_t; +typedef uint32_t char32_t; +#endif // !c++11 + +namespace miosix { + +/** + * Result codes for unicode related conversion stuff + */ +class Unicode +{ +public: + /** + * Possible errors for unicode string conversion + */ + enum error + { + OK, ///< The string conversion completed successfully + INSUFFICIENT_SPACE, ///< The source string is too long to fit + INVALID_STRING ///< The source string is an illegal unicode string + }; + + /// Represents an invalid code point + static const char32_t invalid=0xffffffff; + + /** + * Peek an unicode code point out of an iterator into an utf8 string + * \param it an iterator into an utf8 encoded string + * \param end iterator one past the last character of the string + * \return an unicode code point, or Unicode::invalid if the string + * contains an invalid code point. Returns 0 if the end of string is found, + * and it is not in the middle of a character + */ + template + static char32_t nextUtf8(Iter& it, Iter end) + { + return nextUtf8(it,end,true); + } + + /** + * Peek an unicode code point out of an iterator into an utf8 string + * \param it an iterator into an utf8 encoded string, the string is assumed + * to be nul-terminated + * \return an unicode code point, or Unicode::invalid if the string + * contains an invalid code point. Returns 0 if the end of string is found, + * and it is not in the middle of a character + */ + template + static char32_t nextUtf8(Iter& it) + { + return nextUtf8(it,it,false); + } + + /** + * Put an unicode code point into a character array, converting it to utf8. + * \param dst pointer to the buffer where the character is to be written + * \param c an unicode code point (utf32 char) + * \param dstSize number of bytes available in dst + * \return an error code and the number of bytes of dst that were used up to + * write src to dst + */ + static std::pair putUtf8(char *dst, char32_t c, int dstSize); + + /** + * Convert an utf8 string in an utf16 one + * \param dst an utf16 string in system-dependent endianness (i.e: little + * endian in a little endian machine and big endian in a big endian one) + * \param dstSize size in units of char16_t of dst, to prevent overflow + * \param src a nul-terminated utf8 string + * \return an error code and the length (in units of char16_t) of the + * string written to dst + */ + static std::pair utf8toutf16(char16_t *dst, int dstSize, + const char *src); + + /** + * Convert an utf16 string in an utf8 one + * \param dst an utf8 string + * \param dstSize size in bytes of dst, to prevent overflow + * \param src a nul-terminated utf16 string in system-dependent endianness + * (i.e: little endian in a little endian machine and big endian in a big + * endian one) + * \return an error code and the length of the string written to dst + */ + static std::pair utf16toutf8(char *dst, int dstSize, + const char16_t *src); + + /** + * \param str an utf8 encoded string + * \return a pair with a bool that is true if the string is valid, and the + * string length in bytes, not code points + */ + static std::pair validateUtf8(const char *str); + +private: + /** + * Common implementation of nextUtf8 + * \param it an iterator into an utf8 encoded string + * \param end iterator one past the last character of the string + * \param checkEnd true if there is the need to check for end of string + * considering end. If false, a nul in the char stream is the only end + * condition. + * \return an unicode code point, or Unicode::invalid if the string + * contains an invalid code point. Returns 0 if the end of string is found, + * and it is not in the middle of a character + */ + template + static char32_t nextUtf8(Iter& it, Iter end, bool checkEnd); +}; + +template +char32_t Unicode::nextUtf8(Iter& it, Iter end, bool checkEnd) +{ + //End of string at the beginning, return 0 + if(checkEnd && it==end) return 0; + + //Note: cast to unsigned char to prevent sign extension if *it > 0x7f + char32_t c=static_cast(*it++); + + //Common case first: ASCII + if(c<0x80) return c; + + //If not ASCII, decode to utf32 + int additionalBytes; + if((c & 0xe0)==0xc0) { c &= 0x1f; additionalBytes=1; } //110xxxxx + else if((c & 0xf0)==0xe0) { c &= 0x0f; additionalBytes=2; } //1110xxxx + else if((c & 0xf8)==0xf0) { c &= 0x07; additionalBytes=3; } //11110xxx + else return invalid; + for(int i=0;i(*it++); + //This includes the case next==0 + if((next & 0xc0)!=0x80) return invalid; + c<<=6; + c |= next & 0x3f; + } + //Detect overlong encodings as errors to prevent vulnerabilities + switch(additionalBytes) + { + case 1: + if(c<0x80) return invalid; + break; + case 2: + if(c<0x800) return invalid; + break; + case 3: + if(c<0x10000) return invalid; + break; + } + + //Reserved space for surrogate pairs in utf16 are invalid code points + if(c>=0xd800 && c<= 0xdfff) return invalid; + //Unicode is limited in the range 0-0x10ffff + if(c>0x10ffff) return invalid; + return c; +} + +} //namespace miosix + +#endif //UNICODE_H + diff --git a/lib/miosix-kernel/miosix/util/utf8test b/lib/miosix-kernel/miosix/util/utf8test new file mode 100644 index 00000000..4b8c31ee --- /dev/null +++ b/lib/miosix-kernel/miosix/util/utf8test @@ -0,0 +1 @@ +This is a test è ώ 𝄞 \ No newline at end of file diff --git a/lib/miosix-kernel/miosix/util/util.cpp b/lib/miosix-kernel/miosix/util/util.cpp new file mode 100644 index 00000000..ebf246d7 --- /dev/null +++ b/lib/miosix-kernel/miosix/util/util.cpp @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +/* + * Part of Miosix Embedded OS + * some utilities + */ +#include +#include +#include "util.h" +#include "kernel/kernel.h" +#include "stdlib_integration/libc_integration.h" +#include "config/miosix_settings.h" +#include "arch_settings.h" //For WATERMARK_FILL and STACK_FILL + +using namespace std; + +namespace miosix { + +// +// MemoryStatists class +// + +void MemoryProfiling::print() +{ + unsigned int curFreeStack=getCurrentFreeStack(); + unsigned int absFreeStack=getAbsoluteFreeStack(); + unsigned int stackSize=getStackSize(); + unsigned int curFreeHeap=getCurrentFreeHeap(); + unsigned int absFreeHeap=getAbsoluteFreeHeap(); + unsigned int heapSize=getHeapSize(); + + iprintf("Stack memory statistics.\n" + "Size: %u\n" + "Used (current/max): %u/%u\n" + "Free (current/min): %u/%u\n" + "Heap memory statistics.\n" + "Size: %u\n" + "Used (current/max): %u/%u\n" + "Free (current/min): %u/%u\n", + stackSize,stackSize-curFreeStack,stackSize-absFreeStack, + curFreeStack,absFreeStack, + heapSize,heapSize-curFreeHeap,heapSize-absFreeHeap, + curFreeHeap,absFreeHeap); +} + +unsigned int MemoryProfiling::getStackSize() +{ + return miosix::Thread::getStackSize(); +} + +unsigned int MemoryProfiling::getAbsoluteFreeStack() +{ + const unsigned int *walk=miosix::Thread::getStackBottom(); + const unsigned int stackSize=miosix::Thread::getStackSize(); + unsigned int count=0; + while(count(stack_ptr) + - reinterpret_cast(walk)); + //This takes into account CTXSAVE_ON_STACK. + if(freeStack<=CTXSAVE_ON_STACK) return 0; + return freeStack-CTXSAVE_ON_STACK; +} + +unsigned int MemoryProfiling::getHeapSize() +{ + //These extern variables are defined in the linker script + //Pointer to begin of heap + extern const char _end asm("_end"); + //Pointer to end of heap + extern const char _heap_end asm("_heap_end"); + + return reinterpret_cast(&_heap_end) + - reinterpret_cast(&_end); +} + +unsigned int MemoryProfiling::getAbsoluteFreeHeap() +{ + //This extern variable is defined in the linker script + //Pointer to end of heap + extern const char _heap_end asm("_heap_end"); + + unsigned int maxHeap=getMaxHeap(); + + return reinterpret_cast(&_heap_end) - maxHeap; +} + +unsigned int MemoryProfiling::getCurrentFreeHeap() +{ + struct mallinfo mallocData=_mallinfo_r(__getreent()); + return getHeapSize()-mallocData.uordblks; +} + +/** + * \internal + * used by memDump + */ +static void memPrint(const char *data, char len) +{ + iprintf("0x%08x | ",reinterpret_cast(data)); + for(int i=0;i=32)&&(data[i]<127)) iprintf("%c",data[i]); + else iprintf("."); + } + iprintf("\n"); +} + +void memDump(const void *start, int len) +{ + const char *data=reinterpret_cast(start); + while(len>16) + { + memPrint(data,16); + len-=16; + data+=16; + } + if(len>0) memPrint(data,len); +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/util/util.h b/lib/miosix-kernel/miosix/util/util.h new file mode 100644 index 00000000..39030f61 --- /dev/null +++ b/lib/miosix-kernel/miosix/util/util.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +/*********************************************************************** +* util.h Part of the Miosix Embedded OS. +* A collection of "utilities". +************************************************************************/ + +#ifndef UTIL_H +#define UTIL_H + +namespace miosix { + +/** + * \addtogroup Util + * \{ + */ + +/** + * This class allows to gather memory statistics useful when developing + * embedded code. + */ +class MemoryProfiling +{ +public: + + /** + * Prints a summary of the information that can be gathered from this class. + */ + static void print(); + + /** + * \return stack size of current thread. + */ + static unsigned int getStackSize(); + + /** + * \return absolute free stack of current thread.
+ * Absolute free stack is the minimum free stack since the thread was + * created. + */ + static unsigned int getAbsoluteFreeStack(); + + /** + * \return current free stack of current thread.
+ * Current free stack is the free stack at the moment when the this + * function is called. + */ + static unsigned int getCurrentFreeStack(); + + /** + * \return heap size which is defined in the linker script.
The heap is + * shared among all threads, therefore this function returns the same value + * regardless which thread is called in. + */ + static unsigned int getHeapSize(); + + /** + * \return absolute (not current) free heap.
+ * Absolute free heap is the minimum free heap since the program started. + *
The heap is shared among all threads, therefore this function returns + * the same value regardless which thread is called in. + */ + static unsigned int getAbsoluteFreeHeap(); + + /** + * \return current free heap.
+ * Current free heap is the free heap at the moment when the this + * function is called.
+ * The heap is shared among all threads, therefore this function returns + * the same value regardless which thread is called in. + */ + static unsigned int getCurrentFreeHeap(); + +private: + //All member functions static, disallow creating instances + MemoryProfiling(); +}; + +/** + * Dump a memory area in this format + * 0x00000000 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ + * \param start pointer to beginning of memory block to dump + * \param len length of memory block to dump + */ +void memDump(const void *start, int len); + +/** + * \} + */ + +} //namespace miosix + +#endif //UTIL_H diff --git a/lib/miosix-kernel/miosix/util/version.cpp b/lib/miosix-kernel/miosix/util/version.cpp new file mode 100644 index 00000000..2914b142 --- /dev/null +++ b/lib/miosix-kernel/miosix/util/version.cpp @@ -0,0 +1,74 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include "config/miosix_settings.h" +#include "board_settings.h" + +// These two #if are here because version checking for config files in +// out-of-git-tree projects has to be done somewhere. + +#if MIOSIX_SETTINGS_VERSION != 100 +#error You need to update miosix_settings.h to match the version in the kernel. +#endif + +#if BOARD_SETTINGS_VERSION != 100 +#error You need to update board_settings.h to match the version in the kernel. +#endif + +namespace miosix { + +#define tts(x) #x +#define ts(x) tts(x) + +#ifdef __clang__ //clang also defines GNUC, so it has to go first +#define CV ", clang " \ + ts(__clang_major__) "." ts(__clang_minor__) "." ts(__clang_patchlevel__) \ + "-mp" ts(_MIOSIX_CLANG_PATCH_VERSION) +#define AU __attribute__((used)) +#elif defined(__GNUC__) +#ifdef _MIOSIX_GCC_PATCH_MAJOR +#define PV ts(_MIOSIX_GCC_PATCH_MAJOR) "." ts(_MIOSIX_GCC_PATCH_MINOR) +#else +#define PV ts(_MIOSIX_GCC_PATCH_VERSION) +#endif +#define CV ", gcc " \ + ts(__GNUC__) "." ts(__GNUC_MINOR__) "." ts(__GNUC_PATCHLEVEL__) \ + "-mp" PV +#define AU __attribute__((used)) +#else +#define CV +#define AU +#endif + +const char AU ver[]="Miosix v2.22 (" _MIOSIX_BOARDNAME ", " __DATE__ " " __TIME__ CV ")"; + +const char *getMiosixVersion() +{ + return ver; +} + +} //namespace miosix diff --git a/lib/miosix-kernel/miosix/util/version.h b/lib/miosix-kernel/miosix/util/version.h new file mode 100644 index 00000000..f1616543 --- /dev/null +++ b/lib/miosix-kernel/miosix/util/version.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2010, 2011, 2012, 2013, 2014 by Terraneo Federico * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * As a special exception, if other files instantiate templates or use * + * macros or inline functions from this file, or you compile this file * + * and link it with other works to produce a work based on this file, * + * this file does not by itself cause the resulting work to be covered * + * by the GNU General Public License. However the source code for this * + * file must still be made available in accordance with the GNU General * + * Public License. This exception does not invalidate any other reasons * + * why a work based on this file might be covered by the GNU General * + * Public License. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef VERSION_H +#define VERSION_H + +namespace miosix { + +/** + * \addtogroup Util + * \{ + */ + +/** + * Allows to know the version of the kernel at runtime. + * \return a string with the kernel version. + * The format is "Miosix vX.XX (board, builddate, compiler)" where + * vX.XX is the kernel version number, like "v2.0" + * board is the board name, like "stm32f103ze_stm3210e-eval" + * builddate is the date the kernel was built, like "Oct 30 2011 00:58:10" + * compiler is the compiler version, like "gcc 4.7.3" + */ +const char *getMiosixVersion(); + +/** + * \} + */ + +} //namespace miosix + +#endif /* VERSION_H */ diff --git a/meson.build b/meson.build index 4d780ff8..02e57ab6 100644 --- a/meson.build +++ b/meson.build @@ -121,22 +121,19 @@ else tinyusb_dep = [ ] endif -## -## RTOS -## -rtos_inc = ['lib/miosix-kernel', - 'lib/miosix-kernel/arch', - 'lib/miosix-kernel/arch/cortexM4_stm32f4/common', - 'lib/miosix-kernel/arch/cortexM4_stm32f4/M4_openrtx', - 'lib/miosix-kernel/config/arch/cortexM4_stm32f4/M4_openrtx'] - src = openrtx_src + main_src + minmea_src inc = openrtx_inc + minmea_inc + qdec_inc +## +## RTOS +## +subdir('lib/miosix-kernel') + # Include RTOS only for embedded targets if meson.is_cross_build() - inc = inc + rtos_inc - def = def + {'DONT_USE_CMSIS_INIT': ''} + inc = inc + miosix_cm4f_inc + src = src + miosix_cm4f_src + def = def + miosix_cm4f_def endif ## Add git commit or tag to print in OpenRTX @@ -228,8 +225,7 @@ stm32f405_inc = ['platform/mcu/CMSIS/Include', 'platform/mcu/STM32F4xx/drivers', 'platform/mcu/STM32F4xx/drivers/usb'] -stm32f405_def = {'STM32F405xx': '', 'HSE_VALUE':'8000000', - '_POSIX_PRIORITY_SCHEDULING':''} +stm32f405_def = {'STM32F405xx': '', 'HSE_VALUE':'8000000'} ## ## MK22FN512 @@ -252,9 +248,10 @@ mk22fn512_src = ['platform/mcu/MK22FN512xxx12/boot/startup.cpp', mk22fn512_inc = ['platform/mcu/CMSIS/Include', 'platform/mcu/CMSIS/Device/NXP/MK22FN512xxx12/Include', + 'platform/mcu/MK22FN512xxx12', 'platform/mcu/MK22FN512xxx12/drivers'] -mk22fn512_def = {'_POSIX_PRIORITY_SCHEDULING':''} +mk22fn512_def = {} ## ## ----------------------- Platform specializations ---------------------------- diff --git a/platform/mcu/MK22FN512xxx12/boot/arch_registers_impl.h b/platform/mcu/MK22FN512xxx12/boot/arch_registers_impl.h new file mode 100644 index 00000000..7558d4de --- /dev/null +++ b/platform/mcu/MK22FN512xxx12/boot/arch_registers_impl.h @@ -0,0 +1,11 @@ + +#ifndef ARCH_REGISTERS_IMPL_H +#define ARCH_REGISTERS_IMPL_H + +#include "MK22F51212.h" +#include "core_cm4.h" +#include "system_MK22F51212.h" + +#define RCC_SYNC() //Workaround for a bug in stm32f42x + +#endif //ARCH_REGISTERS_IMPL_H diff --git a/platform/mcu/MK22FN512xxx12/boot/bsp.cpp b/platform/mcu/MK22FN512xxx12/boot/bsp.cpp index 58b93177..98fb078e 100644 --- a/platform/mcu/MK22FN512xxx12/boot/bsp.cpp +++ b/platform/mcu/MK22FN512xxx12/boot/bsp.cpp @@ -41,6 +41,9 @@ namespace miosix void IRQbspInit() { SIM->SCGC5 |= 0x3E00; // Enable GPIO clock + + // Configure SysTick + SysTick->LOAD = SystemCoreClock / miosix::TICK_FREQ; } void bspInit2() diff --git a/platform/mcu/STM32F4xx/boot/arch_registers_impl.h b/platform/mcu/STM32F4xx/boot/arch_registers_impl.h new file mode 100644 index 00000000..7bb97ab4 --- /dev/null +++ b/platform/mcu/STM32F4xx/boot/arch_registers_impl.h @@ -0,0 +1,12 @@ + +#ifndef ARCH_REGISTERS_IMPL_H +#define ARCH_REGISTERS_IMPL_H + +//Always include stm32f4xx.h before core_cm4.h, there's some nasty dependency +#include "stm32f4xx.h" +#include "core_cm4.h" +#include "system_stm32f4xx.h" + +#define RCC_SYNC() //Workaround for a bug in stm32f42x + +#endif //ARCH_REGISTERS_IMPL_H diff --git a/platform/mcu/STM32F4xx/boot/bsp.cpp b/platform/mcu/STM32F4xx/boot/bsp.cpp index 9799f870..18eeb85f 100644 --- a/platform/mcu/STM32F4xx/boot/bsp.cpp +++ b/platform/mcu/STM32F4xx/boot/bsp.cpp @@ -40,9 +40,9 @@ namespace miosix void IRQbspInit() { - RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN - | RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN - | RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_GPIOHEN; + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN + | RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN + | RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_GPIOHEN; RCC_SYNC(); GPIOA->OSPEEDR=0xaaaaaaaa; //Default to 50MHz speed for all GPIOS @@ -68,6 +68,9 @@ void IRQbspInit() usart3_init(115200); usart3_IRQwrite("starting...\r\n"); #endif + + // Configure SysTick + SysTick->LOAD = SystemCoreClock / miosix::TICK_FREQ; } void bspInit2()