- WIP: OMAP35x SD/MMC controller driver

- WIP: Pagecache
- Added sleep() and usleep() functions
This commit is contained in:
2013-07-19 19:51:40 +02:00
parent 3b5c555117
commit 76cc09a168
22 changed files with 1212 additions and 29 deletions

View File

@@ -5,10 +5,10 @@ OBJCOPY=arm-none-eabi-objcopy
OBJDUMP=arm-none-eabi-objdump
#CFLAGS=-ffreestanding -march=armv7-a -mcpu=cortex-a8 -mfloat-abi=softfp -mfpu=neon --std=gnu99 -ggdb -Wall -Wextra -pedantic -Wno-unused-parameter -Og -flto
CXXFLAGS=-ffreestanding -march=armv7-a -mcpu=cortex-a8 -mfloat-abi=softfp -mfpu=neon --std=c++11 -ggdb -Wall -Wextra -pedantic -Wno-unused-parameter -fno-rtti -fstrict-enums -Wabi -Og -flto
CXXFLAGS=-ffreestanding -march=armv7-a -mcpu=cortex-a8 -mfloat-abi=softfp -mfpu=neon -mno-unaligned-access --std=c++11 -ggdb -Wall -Wextra -pedantic -Wno-unused-parameter -fno-rtti -fstrict-enums -Wabi -Og -flto
LDFLAGS=-static
C_SRCS=
CXX_SRCS=cortexa8.cc drv_omap35x_gpt.cc drv_omap35x_i2c.cc drv_tps65950.cc main.cc mm.cc newlib_syscall.cc omap35x.cc omap35x_intc.cc omap35x_prcm.cc phys_mm.cc scheduler.cc syscall.cc uart.cc
CXX_SRCS=cortexa8.cc drv_omap35x_gpt.cc drv_omap35x_i2c.cc drv_omap35x_sdmmc.cc drv_tps65950.cc main.cc mm.cc newlib_syscall.cc omap35x.cc omap35x_intc.cc omap35x_prcm.cc pagecache.cc phys_mm.cc scheduler.cc sleep.cc syscall.cc uart.cc
S_SRCS=cortexa8_asm.s
OBJS=$(addprefix objs/,$(C_SRCS:.c=.o)) $(addprefix objs/,$(CXX_SRCS:.cc=.o)) $(addprefix objs/,$(S_SRCS:.s=.o))
@@ -29,7 +29,7 @@ beagle-nand.bin: fw.img
./bb_nandflash_ecc $@ 0x0 0xe80000 || true
qemu: beagle-nand.bin
qemu-system-arm -M beagle -m 256M -mtdblock beagle-nand.bin -nographic -s
qemu-system-arm -M beagle -m 256M -mtdblock beagle-nand.bin -sd sd.bin -nographic -s
objs/%.o: %.c
$(CXX) $(CXXFLAGS) -c -MMD -MP -o $@ $<

View File

@@ -78,6 +78,13 @@ void cortexa8::disable_dcache() noexcept {
: [reg] "=r"(reg));
}
void cortexa8::errata() noexcept {
// Errata 458693: Disable PLD and enable L1NEON
__asm__ __volatile__ ("mrc 15, 0, r0, c1, c0, 1; orr r0, r0, #0x220; mcr 15, 0, r0, c1, c0, 1"
: : : "r0");
}
struct ptentry_section_t {
unsigned :1;
unsigned one:1;
@@ -372,15 +379,15 @@ void cortexa8::unmap_pages(uintptr_t virt, unsigned count) {
// Clear L2 pagetable entries
for(unsigned l1 = virt_l1;l1 <= virt_end_l1;++l1) {
assert(ttable1_virt[l1] != nullptr);
for(unsigned l2 = (l1==virt_l1)?virt_l2:0;l2 <= (l1==virt_end_l1)?virt_end_l2:255;++l2) {
for(unsigned l2 = ((l1==virt_l1)?virt_l2:0);l2 <= ((l1==virt_end_l1)?virt_end_l2:255);++l2) {
ttable1_virt[l1][l2] = 0;
}
}
// Flush L2 pagetables from L1D$
for(unsigned l1 = virt_l1;l1 <= virt_end_l1;++l1) {
for(unsigned cl = (l1==virt_l1)?virt_l2/16:0;
cl <= (l1==virt_end_l1)?(virt_end_l2/16+((virt_end_l2%16==0)?0:1)):15;
for(unsigned cl = ((l1==virt_l1)?virt_l2/16:0);
cl <= ((l1==virt_end_l1)?(virt_end_l2/16+((virt_end_l2%16==0)?0:1)):15);
++cl) {
__asm__ __volatile__ ("mcr 15, 0, %[val], c7, c11, 1"
: : [val] "r"(ttable1_virt[l1]+cl*16));
@@ -429,9 +436,9 @@ void _cortexa8_excp_data_abt() {
__asm__ ("mov %[lr], lr; mrc 15, 0, %[dfsr], c5, c0, 0; mrc 15, 0, %[dfar], c6, c0, 0"
: [lr] "=r"(lr), [dfsr] "=r"(dfsr), [dfar] "=r"(dfar));
printf("ERROR: Data abort\n");
printf("PC: %.8lx Fault Address: %.8lx Fault code: %.lx\n",
lr-8, dfar, dfsr&0x4);
while(1) {}
printf("PC: %.8lx Fault Address: %.8lx Fault code: %lx\n",
lr-4, dfar, ((dfsr>>7)&0x20) | ((dfsr>>6)&0x10) | (dfsr&0xf));
while(1) { __asm__ __volatile__ ("wfi"); }
}
void _cortexa8_excp_pf_abt() {
@@ -439,9 +446,9 @@ void _cortexa8_excp_pf_abt() {
__asm__ ("mov %[lr], lr; mrc 15, 0, %[ifsr], c5, c0, 1; mrc 15, 0, %[ifar], c6, c0, 2"
: [lr] "=r"(lr), [ifsr] "=r"(ifsr), [ifar] "=r"(ifar));
printf("ERROR: Prefetch abort\n");
printf("PC: %.8lx Fault Address: %.8lx Fault code: %.lx\n",
lr-4, ifar, ifsr&0x4);
while(1) {}
printf("PC: %.8lx Fault Address: %.8lx Fault code: %lx\n",
lr-4, ifar, ((ifsr>>7)&0x20) | ((ifsr>>6)&0x10) | (ifsr&0xf));
while(1) { __asm__ __volatile__ ("wfi"); }
}
void _cortexa8_excp_undef() {

View File

@@ -38,6 +38,8 @@ namespace cortexa8 {
void disable_icache() noexcept;
void disable_dcache() noexcept;
void errata() noexcept;
void init_mmu() noexcept;
void init_handlers() noexcept;

View File

@@ -21,7 +21,7 @@
class OMAP35x_GPT_impl {
public:
OMAP35x_GPT_impl(uintptr_t base, int irq) : base_{base}, irq_{irq} {
OMAP35x_GPT_impl(uintptr_t base, int irq) : base_{base}, irq_{irq}, ticks_(0) {
}
~OMAP35x_GPT_impl() {
@@ -46,8 +46,26 @@ public:
global::intc->enable_int(irq_);
}
void ustimer() {
r_tiocp_cfg() = 0x215; // Clockactiviy = 2, emufree = 0, idlemode = 2 (smartidle), wakeup = ena, autoidle = 1
r_tldr() = 0;
r_tcrr() = 0;
r_tclr() = 0x3; // autoreload = 1, start = 1
}
uint32_t getTicks() const {
return ticks_;
}
uint32_t getCounter() {
return r_tcrr();
}
private:
void irqhandler() {
++ticks_;
if(handler_)
handler_();
@@ -78,6 +96,7 @@ private:
MMIO_alloc base_;
int irq_;
int_handler_t handler_;
volatile uint32_t ticks_;
};
@@ -90,3 +109,15 @@ OMAP35x_GPT::~OMAP35x_GPT() {
void OMAP35x_GPT::ticktimer(int_handler_t handler) {
impl_->ticktimer(handler);
}
uint32_t OMAP35x_GPT::getTicks() const {
return impl_->getTicks();
}
void OMAP35x_GPT::ustimer() {
impl_->ustimer();
}
uint32_t OMAP35x_GPT::getCounter() {
return impl_->getCounter();
}

View File

@@ -6,6 +6,9 @@
#include "omap35x_intc.hh"
#define USTIMER_PER_US 26
#define TICKTIMER_MS 10
class OMAP35x_GPT_impl;
class OMAP35x_GPT {
@@ -18,6 +21,14 @@ public:
// The GPT must be GPTIMER1, GPTIMER2 or GPTIMER10 because the 1ms-Tick functionality is needed
void ticktimer(int_handler_t handler);
uint32_t getTicks() const;
// Configure the GPT as the system microsecond timer
// The GPT counts up at USTIMER_PER_US counts/us
void ustimer();
uint32_t getCounter();
private:
std::unique_ptr<OMAP35x_GPT_impl> impl_;
};

618
drv_omap35x_sdmmc.cc Normal file
View File

@@ -0,0 +1,618 @@
#include <cstdint>
#include <errno.h>
#include <cstdio>
#include <vector>
#include "exceptions.hh"
#include "sleep.hh"
#include "globals.hh"
#include "drv_omap35x_gpt.hh"
#include "omap35x_intc.hh"
#include "omap35x_prcm.hh"
#include "drv_omap35x_sdmmc.hh"
class commanderror : public ex::error {
public:
commanderror(uint32_t stat) : ex::error(EIO), stat(stat) {
}
uint32_t stat;
};
class dataerror : public ex::error {
public:
dataerror(uint32_t stat) : ex::error(EIO), stat(stat) {
}
uint32_t stat;
};
#define ACMD(x) ((((x)) | 0x100))
typedef union {
uint32_t data[4];
struct {
unsigned :8;
unsigned mdt_m:4;
unsigned mdt_y:8;
unsigned :4;
unsigned psn:32;
unsigned prv:8; // BCD coded
char pnm[5];
char oid[2];
unsigned mid:8;
} __attribute__((packed)) bits;
} CID_reg;
typedef union {
uint32_t data[4];
struct {
unsigned :10;
unsigned file_format:2;
unsigned tmp_write_protect:1;
unsigned perm_write_protect:1;
unsigned copy:1;
unsigned file_format_grp:1;
unsigned :5;
unsigned write_bl_partial:1;
unsigned write_bl_len:4;
unsigned r2w_factor:3;
unsigned :2;
unsigned wp_grp_enable:1;
unsigned wp_grp_size:7;
unsigned sector_size:7;
unsigned erase_blk_en:1;
unsigned c_size_mult:3;
unsigned vdd_w_curr_max:3;
unsigned vdd_w_curr_min:3;
unsigned vdd_r_curr_max:3;
unsigned vdd_r_curr_min:3;
unsigned c_size:12;
unsigned :2;
unsigned dsr_imp:1;
unsigned read_blk_misalign:1;
unsigned write_blk_misalign:1;
unsigned read_bl_partial:1;
unsigned read_bl_len:4;
unsigned ccc:12;
unsigned tran_speed:8;
unsigned nsac:8;
unsigned taac:8;
unsigned :6;
unsigned csd_structure:2;
} __attribute__((packed)) v1;
struct {
unsigned :12;
unsigned tmp_write_protect:1;
unsigned perm_write_protect:1;
unsigned copy:1;
unsigned :32;
unsigned :1;
unsigned c_size:22;
unsigned :6;
unsigned dsr_imp:1;
unsigned :7;
unsigned ccc:12;
unsigned :30;
unsigned csd_structure:2;
} __attribute__((packed)) v2;
} CSD_reg;
#define BCDDEC(x) (((((x))&0xf) + (((((x))>>4)&0xf)*10)))
OMAP35x_SDMMC::OMAP35x_SDMMC(uintptr_t base, unsigned irq, unsigned drq_rx, unsigned drq_tx)
: base_{base}, irq_(irq), drq_rx_(drq_rx), drq_tx_(drq_tx), rca_(0), intSem_(0) {
printf("OMAP35x SD/MMC controller\n");
// Reset the controller
r_sysconfig() |= (1<<1);
while(!(r_sysstatus() & (1<<0)));
r_sysctl() |= (1<<24); // Software reset all
while(r_sysctl() & (1<<24));
// Set capabilities
uint32_t capa = (1<<26);
if(base_.get_phys() == 0x4809c000) { // MMC 1
printf("MMCHS1, activating 3.0V support\n");
capa |= (1<<25);
}
r_capa() |= capa;
// Install interrupt handler
global::intc->register_handler(irq_, std::bind(&OMAP35x_SDMMC::int_handler, this), 0);
global::intc->enable_int(irq_);
// Configure
r_hctl() = (r_hctl() & ~(0x7<<9)) | (0x6<<9); // 3.0V mode
r_hctl() |= (1<<8); // power on
while(!(r_hctl() & (1<<8)));
// TODO: padconf
changeClock(0x3ff);
r_ie() = (1<<28) | (1<<22) | (1<<21) | (1<<20) |(1<<19) | (1<<18) | (1<<17) | (1<<16) | (1<<5) | (1<<1) | (1<<0);
// Card detect
r_con() |= (1<<1); // Send initialization stream
r_cmd() = 0x0; // Dummy command
usleep(1000);
r_stat() |= (1<<0);
r_con() &= ~(1<<1); // End initialization stream
r_stat() = 0xffffffff;
// Set clock to ~400 kHz
changeClock(240);
cmd_reset();
r_ise() = (1<<28) | (1<<22) | (1<<21) // | (1<<20)
|(1<<19) | (1<<18) | (1<<17) | (1<<16) // | (1<<5) | (1<<1)
| (1<<0);
printf("Probing SD/MMC card...\n");
try {
// Send CMD0
command(0, 0);
// Here we could test for SDIO (send CMD5), but we don't support SDIO (yet)
bool sdv2 = false;
uint32_t resp;
try {
resp = command_r1(8, 0x000001a5);
if((resp&0xfff) != 0x1a5) {
printf("CMD8 unexpected response: %.8lx\n", resp);
throw ex::error{EIO};
} else
sdv2 = true;
} catch(commanderror &ex) {
printf("CMD8 failed: %.8lx\n", ex.stat);
} catch(ex::timeout &ex) {
printf("CMD8 timed out\n");
cmd_reset();
}
bool sd = false;
uint32_t startTicks = global::ticktimer->getTicks(), endTicks;
uint32_t arg = (sdv2?0x50000000:0);
while((endTicks = global::ticktimer->getTicks()) < (startTicks+1000/TICKTIMER_MS)) { // Try for 1s
try {
resp = command_r1(ACMD(41), arg);
arg |= (0x1ff<<15); // Set OCR bits after first ACMD41
} catch (ex::timeout &ex) {
break; // On command timeout we don't have to continue trying
}
if(resp & (1<<31)) {
printf("ACMD41 resp: %.8lx after %lu ms\n", resp, (endTicks-startTicks)*TICKTIMER_MS);
sd = true;
break;
}
}
if(!sd) { // Try MMC init
assert(!sdv2);
arg = 0;
r_con() |= (1<<0); // Open drain
startTicks = global::ticktimer->getTicks();
while((endTicks = global::ticktimer->getTicks()) < startTicks+1000/TICKTIMER_MS) {
try {
resp = command_r1(1, 0);
arg |= (0x1ff<<15); // Set OCR bits after first CMD1
} catch (ex::timeout &ex) {
// On command timeout we don't have to continue trying
// Unknown/Unsupported card type
throw ex::error{EIO};
}
if(resp & (1<<31)) {
printf("CMD1 resp: %.8lx after %lu ms\n", resp, (endTicks-startTicks)*TICKTIMER_MS);
break;
}
}
}
std::array<uint32_t, 4> resp4 = command_r2(2, 0);
CID_reg cid;
for(int i = 0;i <4;++i)
cid.data[i] = resp4[i];
printf(" %c%c %c%c%c%c%c Rev %hhu Serial# %.8x Date %u/%u\n",
cid.bits.oid[1], cid.bits.oid[0],
cid.bits.pnm[4], cid.bits.pnm[3], cid.bits.pnm[2], cid.bits.pnm[1], cid.bits.pnm[0],
cid.bits.prv, cid.bits.psn, cid.bits.mdt_m, 2000+cid.bits.mdt_y);
resp = command_r1(3, 0);
resp &= 0xffff0000;
rca_ = resp>>16;
r_con() &= ~(1<<0); // No more open drain
resp4 = command_r2(9, rca_<<16);
printf("CSD: %.8lx %.8lx %.8lx %.8lx\n",
resp4[3], resp4[2], resp4[1], resp4[0]);
CSD_reg csd;
for(int i = 0;i <4;++i)
csd.data[i] = resp4[i];
if((csd.v1.csd_structure == 1) && sdv2) {
blockSize_ = 512;
sectorSize_ = 64*1024;
cardBlocks_ = (csd.v2.c_size+1)*1024;
} else {
assert(csd.v1.csd_structure == 0);
blockSize_ = _pow2(csd.v1.read_bl_len);
sectorSize_ = blockSize_*(csd.v1.sector_size+1);
cardBlocks_ = (csd.v1.c_size+1)*_pow2(csd.v1.c_size_mult+2);
}
if(!sd) {
changeClock(5); // 19.2 MHz
writeTimeout_ = 11; // ~500 ms
readTimeout_ = 8; // ~100 ms
} else {
// TODO: Check card for High-Speed (50 MHz) support
changeClock(4); // 24 MHz
writeTimeout_ = 11; // ~ 500 ms
readTimeout_ = 9; // ~ 100 ms
}
if(sd) {
if(sdv2) {
if(csd.v1.csd_structure == 1)
if(cardBlocks_ >= 67108864u) // 32GiB
cardType_ = CardType::sdxc;
else
cardType_ = CardType::sdhc;
else
cardType_ = CardType::sdv2;
} else
cardType_ = CardType::sdv1;
} else
cardType_ = CardType::mmc;
printf("Detected %s card, %u KiB (%u byte Blocks, %u byte Sectors)\n",
cardTypeToString(), cardBlocks_*blockSize_/1024, blockSize_, sectorSize_);
resp = command_r1b(7, rca_<<16);
printf("%.8lx\n", resp);
if(cardType_ != CardType::mmc) {
resp = command_r1(ACMD(6), 0x2); // Set 4 byte bus on card
r_hctl() |= (1<<1); // Set 4 byte bus on controller
}
resp = command_r1(16, 512);
printf("%.8lx\n", resp);
try {
test_data(0);
} catch(dataerror &ex) {
printf("Data error: %.8lx\n", ex.stat);
}
} catch(commanderror &ex) {
printf("Unexpected command error: %.8lx\n", ex.stat);
throw;
}
}
OMAP35x_SDMMC::~OMAP35x_SDMMC() {
global::intc->disable_int(irq_);
}
unsigned OMAP35x_SDMMC::getBlockSize() const {
return blockSize_;
}
void OMAP35x_SDMMC::write(unsigned pos, char const* data, unsigned count) {
throw ex::error{ENOTSUP};
}
void OMAP35x_SDMMC::read(unsigned pos, char *data, unsigned count) {
throw ex::error{ENOTSUP};
}
void OMAP35x_SDMMC::command(unsigned cmd, uint32_t arg) {
assert(intSem_.getCount() == 0);
if(cmd&0x100) {
acmd();
cmd &= 0xff;
}
assert(cmd <= 63);
while(r_pstate() & (1<<0)); // Wait for command line free
r_con() &= ~(1<<6) & ~(1<<3);
r_csre() = 0x0;
r_blk() &= 0x0000f800; // No data transfer
r_stat() = 0xffffffff;
r_arg() = arg;
uint32_t creg = (cmd<<24);
if(cmd == 12)
creg |= (0x3<<22);
creg |= // (1<<19) |
(0<<16);
r_cmd() = creg;
intSem_.acquire();
if(intStat_ & (1<<16)) {
cmd_reset();
throw ex::timeout{};
} else if(intStat_ & (1<<15)) {
throw commanderror{intStat_};
}
assert(intStat_ & (1<<0));
}
uint32_t OMAP35x_SDMMC::command_r1(unsigned cmd, uint32_t arg) {
assert(intSem_.getCount() == 0);
if(cmd&0x100) {
acmd();
cmd &= 0xff;
}
assert(cmd <= 63);
while(r_pstate() & (1<<0)); // Wait for command line free
r_con() &= ~(1<<6) & ~(1<<3);
r_csre() = 0x0;
r_blk() &= 0x0000f800; // No data transfer
r_stat() = 0xffffffff;
r_arg() = arg;
uint32_t creg = (cmd<<24);
if(cmd == 12)
creg |= (0x3<<22);
if(!(cmd == 41)) // These commands do not respond with CRC, otherwise
creg |= (1<<19); // CRC check enable
if(!(cmd == 41)) // These commands do not respond with command index, otherwise
creg |= (1<<20); // Index check enable
creg |= (2<<16);
r_cmd() = creg;
intSem_.acquire();
if(intStat_ & (1<<16)) {
cmd_reset();
throw ex::timeout{};
} else if(intStat_ & (1<<15)) {
throw commanderror{intStat_};
}
assert(intStat_ & (1<<0));
return r_rsp10();
}
uint32_t OMAP35x_SDMMC::command_r1b(unsigned cmd, uint32_t arg) {
assert(intSem_.getCount() == 0);
if(cmd&0x100) {
acmd();
cmd &= 0xff;
}
assert(cmd <= 63);
while(r_pstate() & (1<<0)); // Wait for command line free
r_sysctl() = (r_sysctl() & ~(0xf<<16)) | (readTimeout_<<16); // Use read timeout
r_con() &= ~(1<<6) & ~(1<<3);
r_csre() = 0x0;
r_blk() &= 0x0000f800; // No data transfer
r_stat() = 0xffffffff;
r_arg() = arg;
uint32_t creg = (cmd<<24);
if(cmd == 12)
creg |= (0x3<<22);
creg |= (1<<19) | (1<<20); // CRC check, index check enable
creg |= // (1<<19) |
(3<<16);
r_cmd() = creg;
intSem_.acquire();
if(intStat_ & (1<<16)) {
cmd_reset();
throw ex::timeout{};
} else if(intStat_ & (1<<15)) {
throw commanderror{intStat_};
}
assert(intStat_ & (1<<0));
return r_rsp10();
}
std::array<uint32_t, 4> OMAP35x_SDMMC::command_r2(unsigned cmd, uint32_t arg) {
assert(intSem_.getCount() == 0);
if(cmd&0x100) {
acmd();
cmd &= 0xff;
}
assert(cmd <= 63);
while(r_pstate() & (1<<0)); // Wait for command line free
r_con() &= ~(1<<6) & ~(1<<3);
r_csre() = 0x0;
r_blk() &= 0x0000f800; // No data transfer
r_stat() = 0xffffffff;
r_arg() = arg;
uint32_t creg = (cmd<<24);
if(cmd == 12)
creg |= (0x3<<22);
creg |= (1<<19); // CRC check enable
creg |= (1<<16);
r_cmd() = creg;
intSem_.acquire();
if(intStat_ & (1<<16)) {
cmd_reset();
throw ex::timeout{};
} else if(intStat_ & (1<<15)) {
throw commanderror{intStat_};
}
assert(intStat_ & (1<<0));
return std::array<uint32_t, 4>{r_rsp10(), r_rsp32(), r_rsp54(), r_rsp76()};
}
void OMAP35x_SDMMC::int_handler() {
intStat_ = r_stat();
r_stat() = intStat_; // Read and clear status bits
intSem_.release();
global::prcm->clear_wake_core(24);
}
void OMAP35x_SDMMC::acmd() {
uint32_t resp = command_r1(55, rca_<<16);
if(!(resp & (1<<5))) { // Not in ACMD mode
// WTF?
printf("Error: CMD55 response without APP_CMD set (%.8lx)\n", resp);
throw ex::error{EIO};
}
}
void OMAP35x_SDMMC::cmd_reset() {
r_sysctl() |= (1<<25);
while(r_sysctl() & (1<<25));
}
void OMAP35x_SDMMC::dat_reset() {
r_sysctl() |= (1<<26);
while(r_sysctl() & (1<<26));
}
void OMAP35x_SDMMC::changeClock(unsigned cdiv) {
assert(cdiv <= 0x3ff);
r_sysctl() &= ~(1<<2) & ~(1<<0); // Clock disable
r_sysctl() &= ~(0x3ff<<6); // clear clock divider
r_sysctl() |= (cdiv<<6) | (1<<0); // Set new divider and start internal clock
while(!(r_sysctl() & (1<<1))); // Wait until clock stable
r_sysctl() |= (1<<2); // Clock reenable
}
char const* OMAP35x_SDMMC::cardTypeToString() const {
switch(cardType_) {
case CardType::sdxc:
return "SDxc";
case CardType::sdhc:
return "SDhc";
case CardType::sdv2:
return "SDsc v2";
case CardType::sdv1:
return "SDsc v1";
case CardType::mmc:
return "MMC";
default:
return "Unknown";
}
}
void OMAP35x_SDMMC::test_data(unsigned block) {
assert(intSem_.getCount() == 0);
std::vector<uint32_t> buffer(128, 0);
while(r_pstate() & (1<<0)); // Wait for command line free
while(r_pstate() & (1<<1)); // Wait for data line free
r_sysctl() = (r_sysctl() & ~(0xf<<16)) | (readTimeout_<<16); // Use read timeout
r_con() &= ~(1<<6) & ~(1<<3);
r_csre() = 0x0;
r_blk() &= 0x0000f800;
r_blk() |= 0x00010000 | blockSize_;
r_stat() = 0xffffffff;
if((cardType_ == CardType::sdxc) || (cardType_ == CardType::sdhc))
r_arg() = block;
else
r_arg() = block*blockSize_;
uint32_t creg = (17<<24); // CMD17 read single block
creg |= (1<<21) | (1<<20) | (1<<19) | (2<<16) | (1<<4);
r_cmd() = creg;
intSem_.acquire();
if(intStat_ & (1<<16)) {
cmd_reset();
throw ex::timeout{};
} else if(intStat_ & (1<<15)) {
throw commanderror{intStat_};
}
assert(intStat_ & (1<<0));
// TODO: DMA for data transfers
unsigned pos = 0;
while(true) {
uint32_t stat = r_stat() | intStat_; // Make sure no status bits get 'lost'
intStat_ = 0;
if(stat & (1<<15)) {
dat_reset();
throw dataerror{stat};
} else if(stat & (1<<1)) {
break;
} else if(stat & (1<<5)) {
assert(pos < 128);
buffer[pos++] = r_data();
}
}
for(int i = 0;i < 32;++i) {
for(int j = 0;j < 4;++j) {
printf("%.2lx %.2lx %.2lx %.2lx ",
buffer[i*4+j]&0xff,
(buffer[i*4+j]>>8)&0xff,
(buffer[i*4+j]>>16)&0xff,
(buffer[i*4+j]>>24)&0xff);
}
printf("\n");
}
}

90
drv_omap35x_sdmmc.hh Normal file
View File

@@ -0,0 +1,90 @@
#ifndef _DRV_OMAP35X_SDMMC_HH_
#define _DRV_OMAP35X_SDMMC_HH_
#include <cstdint>
#include "interfaces.hh"
#include "exceptions.hh"
#include "mmio.hh"
#include "util.hh"
#include "scheduler.hh"
class OMAP35x_SDMMC : public IBlockDevice {
public:
OMAP35x_SDMMC(uintptr_t base, unsigned irq, unsigned drq_rx, unsigned drq_tx);
~OMAP35x_SDMMC();
// Get the (logical) block size of the device
virtual unsigned getBlockSize() const;
// Write getBlockSize()*'count' bytes of data from 'data' to the device starting at block 'pos'
/* Throw an ex::error on error, containing the errno describing the error. The contents of the
block device from 'pos' to 'pos'+'count' are undefined after an exception */
virtual void write(unsigned pos, char const* data, unsigned count);
// Read getBlockSize()*'count' bytes of data from the device starting at block 'pos' to 'data'
/* Throw an ex::error on error, containing the errno describing the error. The contents of 'data'
are undefined after an exception */
virtual void read(unsigned pos, char *data, unsigned count);
private:
void command(unsigned cmd, uint32_t arg);
uint32_t command_r1(unsigned cmd, uint32_t arg);
uint32_t command_r1b(unsigned cmd, uint32_t arg);
std::array<uint32_t, 4> command_r2(unsigned cmd, uint32_t arg);
void int_handler();
void test_data(unsigned block);
void acmd();
void cmd_reset();
void dat_reset();
void changeClock(unsigned cdiv);
enum class CardType {
sdxc,
sdhc,
sdv2,
sdv1,
mmc
};
char const* cardTypeToString() const;
MMIO_alloc base_;
unsigned irq_, drq_rx_, drq_tx_;
CardType cardType_;
unsigned blockSize_, sectorSize_, cardBlocks_;
unsigned rca_;
unsigned readTimeout_, writeTimeout_;
Scheduler::Semaphore intSem_;
uint32_t intStat_;
uint32_t volatile& r_sysconfig() { return _reg32(base_.get_virt(), 0x10); }
uint32_t volatile& r_sysstatus() { return _reg32(base_.get_virt(), 0x14); }
uint32_t volatile& r_csre() { return _reg32(base_.get_virt(), 0x24); }
uint32_t volatile& r_systest() { return _reg32(base_.get_virt(), 0x28); }
uint32_t volatile& r_con() { return _reg32(base_.get_virt(), 0x2c); }
uint32_t volatile& r_pwcnt() { return _reg32(base_.get_virt(), 0x30); }
uint32_t volatile& r_blk() { return _reg32(base_.get_virt(), 0x104); }
uint32_t volatile& r_arg() { return _reg32(base_.get_virt(), 0x108); }
uint32_t volatile& r_cmd() { return _reg32(base_.get_virt(), 0x10c); }
uint32_t volatile& r_rsp10() { return _reg32(base_.get_virt(), 0x110); }
uint32_t volatile& r_rsp32() { return _reg32(base_.get_virt(), 0x114); }
uint32_t volatile& r_rsp54() { return _reg32(base_.get_virt(), 0x118); }
uint32_t volatile& r_rsp76() { return _reg32(base_.get_virt(), 0x11c); }
uint32_t volatile& r_data() { return _reg32(base_.get_virt(), 0x120); }
uint32_t volatile& r_pstate() { return _reg32(base_.get_virt(), 0x124); }
uint32_t volatile& r_hctl() { return _reg32(base_.get_virt(), 0x128); }
uint32_t volatile& r_sysctl() { return _reg32(base_.get_virt(), 0x12c); }
uint32_t volatile& r_stat() { return _reg32(base_.get_virt(), 0x130); }
uint32_t volatile& r_ie() { return _reg32(base_.get_virt(), 0x134); }
uint32_t volatile& r_ise() { return _reg32(base_.get_virt(), 0x138); }
uint32_t volatile& r_ac12() { return _reg32(base_.get_virt(), 0x13c); }
uint32_t volatile& r_capa() { return _reg32(base_.get_virt(), 0x140); }
uint32_t volatile& r_cur_capa() { return _reg32(base_.get_virt(), 0x148); }
uint32_t volatile& r_rev() { return _reg32(base_.get_virt(), 0x1fc); }
};
#endif

View File

@@ -1,15 +1,36 @@
#ifndef _EXCEPTIONS_HH_
#define _EXCEPTIONS_HH_
#include <errno.h>
namespace ex {
// An unexcpected fatal error occured
class error {
public:
error(int eno = 0) : eno_(eno) {
}
int getErrno() const {
return eno_;
}
private:
int eno_;
};
// A memory allocation, or virtual address space allocation, failed
class bad_alloc{};
class bad_alloc : public error {
public:
bad_alloc() : error(ENOMEM) {
}
};
// An operation timed out
class timeout{};
// An unexcpected fatal error occured
class error{};
class timeout : public error {
public:
timeout() : error(EAGAIN) {
}
};
}
#endif

View File

@@ -190,7 +190,7 @@ SECTIONS
__stack_int = .;
. = . + 0x4000; /* 16KiB syscall stack */
__stack_syscall = .;
. = . + 0x8000; /* 32KiB kernel startup stack */
. = . + 0x10000; /* 64KiB kernel startup stack */
__stack = .;
__end__ = . ;
@@ -222,8 +222,11 @@ SECTIONS
/* Virtual address space for kernel scratchpad */
__scratch_start = 0xc0000000;
/* Map:
0xc0000000 - 0xc0800000 Kernel L2 pagetables
0xc0000000 - 0xc07fffff Kernel L2 pagetables
0xc0800000 - 0xc0ffffff Kernel pagecache
*/
__pc_start = 0xc0800000;
__pc_end = 0xc1000000;
__scratch_end = 0xe0000000;
/* Virtual address space for MMIO */
__io_start = 0xe0000000;

View File

@@ -5,15 +5,20 @@
class OMAP35x_prcm;
class OMAP35x_intc;
class OMAP35x_GPT;
class Scheduler;
class PageCache;
namespace global {
extern OMAP35x_prcm* prcm;
extern OMAP35x_intc* intc;
extern OMAP35x_prcm *prcm;
extern OMAP35x_intc *intc;
extern ICharacterDevice* console;
extern Scheduler* scheduler;
extern OMAP35x_GPT *ticktimer, *ustimer;
extern ICharacterDevice *console;
extern Scheduler *scheduler;
extern PageCache *pagecache;
}
#endif

View File

@@ -9,6 +9,21 @@ public:
virtual int read(char *buf, int const& len) = 0;
};
class IBlockDevice {
public:
// Get the (logical) block size of the device
virtual unsigned getBlockSize() const = 0;
// Write getBlockSize()*'count' bytes of data from 'data' to the device starting at block 'pos'
/* Throw an Error on error, containing the errno describing the error. The contents of the block
device from 'pos' to 'pos'+'count' are undefined after an exception */
virtual void write(unsigned pos, char const* data, unsigned count) = 0;
// Read getBlockSize()*'count' bytes of data from the device starting at block 'pos' to 'data'
/* Throw an Error on error, containing the errno describing the error. The contents of 'data'
are undefined after an exception */
virtual void read(unsigned pos, char *data, unsigned count) = 0;
};
class II2C {
public:
virtual void reg_write(uint8_t slaveid, uint8_t reg, uint8_t value) = 0;

36
main.cc
View File

@@ -7,6 +7,7 @@
#include "drv_omap35x_gpt.hh"
#include "drv_omap35x_i2c.hh"
#include "drv_omap35x_sdmmc.hh"
#include "drv_tps65950.hh"
#include "omap35x.hh"
#include "omap35x_intc.hh"
@@ -16,14 +17,18 @@
#include "scheduler.hh"
#include "cortexa8.hh"
#include "uart.hh"
#include "pagecache.hh"
#include "globals.hh"
namespace global {
OMAP35x_prcm* prcm = nullptr;
OMAP35x_intc* intc = nullptr;
OMAP35x_GPT* ticktimer = nullptr;
OMAP35x_GPT* ustimer = nullptr;
ICharacterDevice* console = nullptr;
Scheduler* scheduler = nullptr;
PageCache *pagecache = nullptr;
}
void sbrk_stats();
@@ -45,6 +50,9 @@ int main(int argc, char* argv[]) {
// Initialize memory
cortexa8::enable_dcache();
cortexa8::enable_icache();
cortexa8::errata();
cortexa8::init_mmu();
// Enable early console
@@ -81,9 +89,12 @@ int main(int argc, char* argv[]) {
if(chipInfo.running_on_qemu())
printf("QEMU detected, avoiding bugs...\n");
OMAP35x_GPT gpt2{0x49032000, 38};
global::ticktimer = new OMAP35x_GPT{0x49032000, 38};
gpt2.ticktimer(&tickfunc);
global::ticktimer->ticktimer(&tickfunc);
global::ustimer = new OMAP35x_GPT{0x49034000, 39};
global::ustimer->ustimer();
// Configure TPS65950 (power control etc.)
OMAP35x_I2C i2c1{0x48070000};
@@ -101,6 +112,26 @@ int main(int argc, char* argv[]) {
global::console = new UART{0x49020000, 74};
global::pagecache = new PageCache{};
OMAP35x_SDMMC *sdmmc1;
try {
sdmmc1 = new OMAP35x_SDMMC{0x4809c000, 83, 61, 60};
} catch(ex::error &ex) {
printf("SD initialization failed: %s (%d)\n", strerror(ex.getErrno()), ex.getErrno());
}
try {
auto test = global::pagecache->readAllocate(*sdmmc1, 0, 1);
for(int i = 0;i < 32;++i) {
for(int j = 0;j < 16;++j)
printf("%.2hhx ", test.data()[i*16+j]);
printf("\n");
}
} catch(ex::error &ex) {
printf("SD read failed: %s (%d)\n", strerror(ex.getErrno()), ex.getErrno());
}
while(1) {
char buf[256];
if(fgets(buf, 256, stdin))
@@ -124,4 +155,3 @@ int main(int argc, char* argv[]) {
return 0;
}

24
mm.cc
View File

@@ -1,12 +1,13 @@
#include <cstdint>
#include <cassert>
#include <cstdio>
#include "cortexa8.hh"
#include "phys_mm.hh"
#include "util.hh"
#include "mm.hh"
extern uint32_t __scratch_start, __scratch_end, __io_start, __io_end, __heap_start, __heap_end;
extern uint32_t __scratch_start, __scratch_end, __io_start, __io_end, __heap_start, __heap_end, __pc_start, __pc_end;
static const uintptr_t scratch_start = (uintptr_t)&__scratch_start;
static const uintptr_t scratch_end = (uintptr_t)&__scratch_end;
@@ -14,8 +15,11 @@ static const uintptr_t io_start = (uintptr_t)&__io_start;
static const uintptr_t io_end = (uintptr_t)&__io_end;
static const uintptr_t heap_start = (uintptr_t)&__heap_start;
static const uintptr_t heap_end = (uintptr_t)&__heap_end;
static const uintptr_t pc_start = (uintptr_t)&__pc_start;
static const uintptr_t pc_end = (uintptr_t)&__pc_end;
static uintptr_t heap_top, io_top;
static uintptr_t heap_top, io_top, pc_top;
void mm::init() {
heap_top = phys_mm::get_end_of_kernel_alloc();
@@ -28,6 +32,7 @@ void mm::init() {
cortexa8::map_pages(heap_start_align, heap_start_align, (heap_top-heap_start)/4096);
io_top = io_start;
pc_top = pc_start;
}
uintptr_t mm::virtalloc_io(unsigned pages) {
@@ -58,3 +63,18 @@ uintptr_t mm::grow_heap(unsigned pages) {
uintptr_t mm::get_heap_end() {
return heap_top;
}
uintptr_t mm::virtalloc_pc(unsigned pages) {
if(pc_top+0x1000*pages >= pc_end)
throw bad_alloc{};
uintptr_t ret = pc_top;
pc_top += 0x1000*pages;
return ret;
}
void mm::virtfree_pc(uintptr_t base) {
printf("NYI: virtfree_pc\n");
}

4
mm.hh
View File

@@ -13,6 +13,10 @@ namespace mm {
// Allocate 'pages' pages of virtual address space in the I/O region
uintptr_t virtalloc_io(unsigned pages);
// Allocate in pagecache region
uintptr_t virtalloc_pc(unsigned pages);
void virtfree_pc(uintptr_t base);
// Grow the kernel heap by 'pages' pages
// Allocate and map the desired amount of memory
// Return the new heap end

View File

@@ -53,6 +53,7 @@ private:
template<class T>
class ScopedLock {
public:
ScopedLock(T& mutex) : mutex_(mutex), done_(false) {
mutex_.lock();
}

View File

@@ -66,6 +66,14 @@ public:
r_mpugrpsel_per() |= (1<<3); // GPT2 wakes up MPU
r_wken_per() |= (1<<3); // GPT2 wake up enable
// Prepare GPTIMER3
r_fclken_per() |= (1<<4); // Enable GPT3 fclk
r_iclken_per() |= (1<<4); // Enable GPT3 iclk
r_autoidle_per() |= (1<<4); // Enable GPT3 iclk autoidle
r_clksel_per() |= (1<<1); // GPT3 fclk = SYS_CLK
r_mpugrpsel_per() &= ~(1<<4); // GPT3 does not wake up MPU
r_wken_per() &= ~(1<<4); // GPT3 wake up disable
// Prepare UART3
r_autoidle_per() |= (1<<11); // Enable UART3 iclk autoidle
r_mpugrpsel_per() |= (1<<11); // UART3 wakes up MPU
@@ -76,6 +84,13 @@ public:
r_iclken1_core() |= (1<<15);
r_autoidle1_core() |= (1<<15);
r_wken1_core() &= ~(1<15);
// Prepare MMC1
r_fclken1_core() |= (1<<24);
r_iclken1_core() |= (1<<24);
r_autoidle1_core() |= (1<<24);
r_wken1_core() |= (1<<24);
r_mpugrpsel1_core() |= (1<<24);
}
void clear_wake_per(int n) {
@@ -83,6 +98,14 @@ public:
r_wkst_per() = (1<<n);
}
void clear_wake_core(int n) {
assert(n >= 0 && n <= 63);
if(n&32)
r_wkst3_core() = (1<<(n&0x1f));
else
r_wkst1_core() = (1<<n);
}
void set_cpu_opp(int opp) {
assert(opp >= 1 && opp <= 6);
@@ -319,6 +342,10 @@ void OMAP35x_prcm::clear_wake_per(int n) {
impl_->clear_wake_per(n);
}
void OMAP35x_prcm::clear_wake_core(int n) {
impl_->clear_wake_core(n);
}
void OMAP35x_prcm::set_cpu_opp(int opp) {
impl_->set_cpu_opp(opp);
}

View File

@@ -14,6 +14,7 @@ public:
void enable_peripherals();
void clear_wake_per(int n);
void clear_wake_core(int n);
void set_cpu_opp(int opp);
void set_core_opp(int opp, int config);

166
pagecache.cc Normal file
View File

@@ -0,0 +1,166 @@
#include <cassert>
#include <cstdio>
#include "mm.hh"
#include "phys_mm.hh"
#include "pagecache.hh"
PageCache::PageCache(int maxPages) : maxPages_(maxPages), curPages_(0) {
}
PageCache::~PageCache() {
for(auto& devEntry : allocs_) { // For each known device
for(auto& allocEntry : devEntry.second) {
freeAlloc(allocEntry);
}
}
}
PageCache::Handle PageCache::readAllocate(IBlockDevice& dev, unsigned pos, unsigned count) {
ScopedLock<Mutex> lock(mutex_);
_allocInfo *alloc = searchAlloc(&dev, pos, count);
if(!alloc) {
_allocInfo newAlloc;
buildAlloc(newAlloc, count);
newAlloc.dev = &dev;
newAlloc.start = pos;
try {
dev.read(newAlloc.start, newAlloc.virtMem, newAlloc.len);
allocs_[&dev].push_back(newAlloc);
alloc = &allocs_[&dev].back();
} catch(...) {
freeAlloc(newAlloc);
throw;
}
curPages_ += count;
}
return Handle(alloc, (pos - alloc->start));
}
PageCache::Handle PageCache::writeAllocate(IBlockDevice& dev, unsigned pos, unsigned count) {
ScopedLock<Mutex> lock(mutex_);
_allocInfo *alloc = searchAlloc(&dev, pos, count);
if(!alloc) {
_allocInfo newAlloc;
buildAlloc(newAlloc, count);
newAlloc.dev = &dev;
newAlloc.start = pos;
try {
allocs_[&dev].push_back(newAlloc);
alloc = &allocs_[&dev].back();
} catch(...) {
freeAlloc(newAlloc);
throw;
}
curPages_ += count;
}
return Handle(alloc, (pos - alloc->start));
}
void PageCache::eject(int lengthHint) {
ScopedLock<Mutex> lock(mutex_);
_eject(lengthHint);
}
void PageCache::freeAlloc(_allocInfo& alloc) {
assert(!alloc.refs);
if(alloc.dirty && alloc.dev)
alloc.dev->write(alloc.start, alloc.virtMem, alloc.len);
if(alloc.virtMem) {
cortexa8::unmap_pages((uintptr_t)alloc.virtMem, alloc.len);
mm::virtfree_pc((uintptr_t)alloc.virtMem);
}
if(alloc.physMem)
phys_mm::free(alloc.physMem);
}
void PageCache::buildAlloc(_allocInfo& alloc, unsigned len) {
if((maxPages_ > 0) && (curPages_ + len > (unsigned)maxPages_)) {
_eject(curPages_+len-maxPages_);
if(curPages_ + len > (unsigned)maxPages_)
throw ex::bad_alloc{};
}
alloc.dirty = false;
alloc.len = len;
alloc.dev = nullptr;
alloc.physMem = 0;
alloc.virtMem = nullptr;
alloc.refs = 0;
bool retry = true;
while(true) {
try {
if(!alloc.physMem)
alloc.physMem = phys_mm::alloc(len);
if(!alloc.virtMem)
alloc.virtMem = (char*)mm::virtalloc_pc(len);
cortexa8::map_pages((uintptr_t)alloc.virtMem, alloc.physMem, len);
break;
} catch(ex::bad_alloc &ex) {
if(retry) {
_eject(len);
retry = false;
} else {
freeAlloc(alloc);
throw;
}
}
}
}
PageCache::_allocInfo* PageCache::searchAlloc(IBlockDevice* dev, unsigned start, unsigned len) {
auto it = allocs_.find(dev);
if(it == allocs_.end())
return nullptr;
for(auto& allocEntry : it->second) {
if((allocEntry.start <= start) &&
((allocEntry.start + allocEntry.len) >= (start + len)))
return &allocEntry;
}
return nullptr;
}
void PageCache::_eject(int lengthHint) {
printf("NYI: PageCache::_eject\n");
}
PageCache::Handle::Handle(_allocInfo* alloc, int ofs) : alloc_(alloc), ofs_(ofs) {
++alloc_->refs;
}
PageCache::Handle::Handle(Handle const& copy) : alloc_(copy.alloc_), ofs_(copy.ofs_) {
++alloc_->refs;
}
PageCache::Handle& PageCache::Handle::operator=(Handle const& copy) {
if(alloc_)
--alloc_->refs;
alloc_ = copy.alloc_;
ofs_ = copy.ofs_;
if(alloc_)
++alloc_->refs;
return *this;
}
PageCache::Handle::~Handle() {
--alloc_->refs;
}

80
pagecache.hh Normal file
View File

@@ -0,0 +1,80 @@
#ifndef _PAGECACHE_HH_
#define _PAGECACHE_HH_
#include <map>
#include <list>
#include "interfaces.hh"
#include "mtprim.hh"
class PageCache {
private:
struct _allocInfo;
public:
class Handle {
public:
Handle() : alloc_(nullptr) {
}
Handle(Handle const& copy);
~Handle();
Handle& operator=(Handle const& copy);
char *data() {
return alloc_->virtMem + ofs_*4096;
}
void setDirty();
void flush();
private:
Handle(_allocInfo* alloc, int ofs);
_allocInfo *alloc_;
int ofs_;
friend class PageCache;
};
PageCache(int maxPages = -1);
~PageCache();
// Allocate memory in the cache and fill it with data from dev
// If the data already exists in the cache a Handle to that entry may be returned
Handle readAllocate(IBlockDevice& dev, unsigned pos, unsigned count);
// Allocate memory in the cache without reading from device
// This prevents useless reads if you're going to overwrite the entire range anyways
// If the data already exists in the cache a Handle to that entry may be returned
Handle writeAllocate(IBlockDevice& dev, unsigned pos, unsigned count);
// Flush all dirty cached pages to their block devices
void sync();
// Eject pages from cache. If lengthHint is > 0, try to free at least that many pages
void eject(int lengthHint);
private:
int maxPages_, curPages_;
Mutex mutex_;
struct _allocInfo {
IBlockDevice *dev;
unsigned start;
unsigned len;
uintptr_t physMem;
char *virtMem;
bool dirty;
int refs;
};
std::map<IBlockDevice*, std::list<_allocInfo> > allocs_;
void freeAlloc(_allocInfo& alloc);
void buildAlloc(_allocInfo& alloc, unsigned len);
_allocInfo* searchAlloc(IBlockDevice* dev, unsigned start, unsigned len);
void _eject(int lengthHint);
};
#endif

View File

@@ -164,6 +164,10 @@ public:
return (count_ < 0);
}
int getCount() const {
return count_;
}
private:
int count_;

35
sleep.cc Normal file
View File

@@ -0,0 +1,35 @@
#include <cassert>
#include <errno.h>
#include "globals.hh"
#include "exceptions.hh"
#include "drv_omap35x_gpt.hh"
#include "cortexa8.hh"
#include "sleep.hh"
#define MAX_USLEEP (4294967295/USTIMER_PER_US)
// "busy" sleep for at least 'us' microseconds with microsecond granularity
void usleep(uint32_t us) {
if(us > MAX_USLEEP)
sleep(us/1000);
uint32_t start = global::ustimer->getCounter();
uint32_t end = start + us*USTIMER_PER_US;
bool overflow = (end<start);
while(true) {
uint32_t now = global::ustimer->getCounter();
if((now >= end) && (!overflow || (now < start)))
return;
}
}
// Sleep with yield, at least 10 ms, 10 ms granularity
void sleep(unsigned ms) {
// TODO: Proper implementation
uint32_t start = global::ticktimer->getTicks();
uint32_t end = start + ms/TICKTIMER_MS;
while(end > global::ticktimer->getTicks()) cortexa8::yield_svc();
}

12
sleep.hh Normal file
View File

@@ -0,0 +1,12 @@
#ifndef _SLEEP_HH_
#define _SLEEP_HH_
#include <cstdint>
// "busy" sleep for at least 'us' microseconds with microsecond granularity
void usleep(uint32_t us);
// Sleep with yield, at least 10 ms, 10 ms granularity
void sleep(unsigned ms);
#endif