Initial read/write support for SCP image files.

This commit is contained in:
Keir Fraser
2018-03-23 15:34:58 +00:00
parent 170137b586
commit d42670b3f4
36 changed files with 5330 additions and 18 deletions

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*.[oa]
.*.d
*~
*.ld
*.elf
*.bin
*.hex
*.orig
*.rej
*.rld
*.upd
*.adf
*.scp
Greaseweazle_*

45
Makefile Normal file
View File

@@ -0,0 +1,45 @@
PROJ = Greaseweazle
VER = v0.0.1a
SUBDIRS += src
.PHONY: all clean dist mrproper flash start serial
ifneq ($(RULES_MK),y)
export ROOT := $(CURDIR)
all:
$(MAKE) -C src -f $(ROOT)/Rules.mk $(PROJ).elf $(PROJ).bin $(PROJ).hex
cp -a src/$(PROJ).hex $(PROJ)-$(VER).hex
clean:
$(MAKE) -f $(ROOT)/Rules.mk $@
dist:
rm -rf $(PROJ)_*
mkdir -p $(PROJ)_$(VER)/reloader
$(MAKE) clean
$(MAKE) all
cp -a $(PROJ)-$(VER).hex $(PROJ)_$(VER)/
$(MAKE) clean
cp -a COPYING $(PROJ)_$(VER)/
cp -a README.md $(PROJ)_$(VER)/
# cp -a RELEASE_NOTES $(PROJ)_$(VER)/
zip -r $(PROJ)_$(VER).zip $(PROJ)_$(VER)
mrproper: clean
rm -rf $(PROJ)_*
endif
BAUD=115200
DEV=/dev/ttyUSB0
flash: all
sudo stm32flash -b $(BAUD) -w src/$(PROJ).hex $(DEV)
start:
sudo stm32flash -b $(BAUD) -g 0 $(DEV)
serial:
sudo miniterm.py $(DEV) 3000000

91
Rules.mk Normal file
View File

@@ -0,0 +1,91 @@
TOOL_PREFIX = arm-none-eabi-
CC = $(TOOL_PREFIX)gcc
OBJCOPY = $(TOOL_PREFIX)objcopy
LD = $(TOOL_PREFIX)ld
ifneq ($(VERBOSE),1)
TOOL_PREFIX := @$(TOOL_PREFIX)
endif
FLAGS = -g -Os -nostdlib -std=gnu99 -iquote $(ROOT)/inc
FLAGS += -Wall -Werror -Wno-format -Wdeclaration-after-statement
FLAGS += -Wstrict-prototypes -Wredundant-decls -Wnested-externs
FLAGS += -fno-common -fno-exceptions -fno-strict-aliasing
FLAGS += -mlittle-endian -mthumb -mcpu=cortex-m3 -mfloat-abi=soft
FLAGS += -Wno-unused-value
ifneq ($(debug),y)
FLAGS += -DNDEBUG
endif
FLAGS += -MMD -MF .$(@F).d
DEPS = .*.d
FLAGS += $(FLAGS-y)
CFLAGS += $(CFLAGS-y) $(FLAGS) -include decls.h
AFLAGS += $(AFLAGS-y) $(FLAGS) -D__ASSEMBLY__
LDFLAGS += $(LDFLAGS-y) $(FLAGS) -Wl,--gc-sections
RULES_MK := y
include Makefile
SUBDIRS += $(SUBDIRS-y)
OBJS += $(OBJS-y) $(patsubst %,%/build.o,$(SUBDIRS))
# Force execution of pattern rules (for which PHONY cannot be directly used).
.PHONY: FORCE
FORCE:
.PHONY: clean
.SECONDARY:
build.o: $(OBJS)
$(LD) -r -o $@ $^
%/build.o: FORCE
$(MAKE) -f $(ROOT)/Rules.mk -C $* build.o
%.o: %.c Makefile
@echo CC $@
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.S Makefile
@echo AS $@
$(CC) $(AFLAGS) -c $< -o $@
%.ld: %.ld.S Makefile
@echo CPP $@
$(CC) -P -E $(AFLAGS) $< -o $@
%.elf: $(OBJS) %.ld Makefile
@echo LD $@
$(CC) $(LDFLAGS) -T$(*F).ld $(OBJS) -o $@
chmod a-x $@
%.hex: %.elf
@echo OBJCOPY $@
$(OBJCOPY) -O ihex $< $@
chmod a-x $@
%.bin: %.elf
@echo OBJCOPY $@
$(OBJCOPY) -O binary $< $@
chmod a-x $@
%.o: $(RPATH)/%.c Makefile
@echo CC $@
$(CC) $(CFLAGS) -c $< -o $@
%.o: $(RPATH)/%.S Makefile
@echo AS $@
$(CC) $(AFLAGS) -c $< -o $@
clean:: $(addprefix _clean_,$(SUBDIRS) $(SUBDIRS-n) $(SUBDIRS-))
rm -f *~ *.o *.elf *.hex *.bin *.ld $(DEPS)
_clean_%: FORCE
$(MAKE) -f $(ROOT)/Rules.mk -C $* clean
-include $(DEPS)

View File

@@ -10,32 +10,32 @@ USB:
PA11 USB_DM
PA12 USB_DP
GPIO: (GPI_float, GPO_pushpull)
PB0 (connect via 1.5kohm to PA12/USB_DP)
PA0 (connect via 1.5kohm to PA12/USB_DP)
Floppy:
-------
GPIn: (GPI_float, active low, 5v tolerant)
PB13 8: IDX
PB4 26: TRK0
GPIn: (GPI_float, active low)
PB6 8: IDX
PB7 26: TRK0
PB8 28: WRPROT
GPOut: (GPO_pushpull, active high)
PA7 2: DENSEL
PA6 12: SEL_A
PA5 16: MTR_A
PA4 18: DIR
PA3 20: STEP
PA1 24: DKWE
PA0 32: SIDE
TimerIn: (GPI_float, active low, 5v tolerant)
PB6 30: DKRD (Timer4/1, CC1:DMA1/1)
TimerOut: (AFO_pushpull, active high)
PA2 22: DKWD (Timer2/3, UP:DMA1/2)
GPOut: (GPO_opendrain or GPO_pushpull)
PB9 2: DENSEL
PB10 12: SEL_A
PB11 16: MTR_A
PB12 18: DIR
PB13 20: STEP
PB14 24: DKWE
PB15 32: SIDE
TimerIn: (GPI_float, active low)
PB3 30: DKRD (Timer2/2, CC2:DMA1/7)
TimerOut: (AFO_opendrain or AFO_pushpull)
PB4 22: DKWD (Timer3/1, UP:DMA1/3)
Unused Pins:
------------
All to be pulled high:
PA8,13-15
PB1-3,5,7,9-12,14-15
PA1-8,13-15
PB0-2,5
PC0-15
Floppy bus connections:

32
inc/cancellation.h Normal file
View File

@@ -0,0 +1,32 @@
/*
* cancellation.h
*
* Asynchronously-cancellable function calls.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
struct cancellation {
uint32_t *sp;
};
#define cancellation_is_active(c) ((c)->sp != NULL)
/* Execute fn() in a wrapped cancellable environment. */
int call_cancellable_fn(struct cancellation *c, int (*fn)(void *), void *arg);
/* From IRQ content: stop running fn() and immediately return -1. */
void cancel_call(struct cancellation *c);
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

34
inc/decls.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* decls.h
*
* Pull in all other header files in an orderly fashion. Source files include
* only this header, and only once.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
#include <stdint.h>
#include <stdarg.h>
#include <stddef.h>
#include "util.h"
#include "stm32f10x_regs.h"
#include "stm32f10x.h"
#include "intrinsics.h"
#include "time.h"
#include "cancellation.h"
#include "timer.h"
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

164
inc/intrinsics.h Normal file
View File

@@ -0,0 +1,164 @@
/*
* intrinsics.h
*
* Compiler intrinsics for ARMv7-M core.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
struct exception_frame {
uint32_t r0, r1, r2, r3, r12, lr, pc, psr;
};
#define _STR(x) #x
#define STR(x) _STR(x)
/* Force a compilation error if condition is true */
#define BUILD_BUG_ON(cond) ({ _Static_assert(!(cond), "!(" #cond ")"); })
#define __aligned(x) __attribute__((aligned(x)))
#define __packed __attribute((packed))
#define always_inline __inline__ __attribute__((always_inline))
#define noinline __attribute__((noinline))
#define likely(x) __builtin_expect(!!(x),1)
#define unlikely(x) __builtin_expect(!!(x),0)
#define illegal() asm volatile (".short 0xde00");
#define barrier() asm volatile ("" ::: "memory")
#define cpu_sync() asm volatile("dsb; isb" ::: "memory")
#define cpu_relax() asm volatile ("nop" ::: "memory")
#define sv_call(imm) asm volatile ( "svc %0" : : "i" (imm) )
#define read_special(reg) ({ \
uint32_t __x; \
asm volatile ("mrs %0,"#reg : "=r" (__x) ::); \
__x; \
})
#define write_special(reg,val) ({ \
uint32_t __x = (uint32_t)(val); \
asm volatile ("msr "#reg",%0" :: "r" (__x) :); \
})
/* CONTROL[1] == 0 => running on Master Stack (Exception Handler mode). */
#define CONTROL_SPSEL 2
#define in_exception() (!(read_special(control) & CONTROL_SPSEL))
#define global_disable_exceptions() \
asm volatile ("cpsid f; cpsid i" ::: "memory")
#define global_enable_exceptions() \
asm volatile ("cpsie f; cpsie i" ::: "memory")
/* NB. IRQ disable via CPSID/MSR is self-synchronising. No barrier needed. */
#define IRQ_global_disable() asm volatile ("cpsid i" ::: "memory")
#define IRQ_global_enable() asm volatile ("cpsie i" ::: "memory")
/* Save/restore IRQ priority levels.
* NB. IRQ disable via MSR is self-synchronising. I have confirmed this on
* Cortex-M3: any pending IRQs are handled before they are disabled by
* a BASEPRI update. Hence no barrier is needed here. */
#define IRQ_save(newpri) ({ \
uint8_t __newpri = (newpri)<<4; \
uint8_t __oldpri = read_special(basepri); \
if (!__oldpri || (__oldpri > __newpri)) \
write_special(basepri, __newpri); \
__oldpri; })
/* NB. Same as CPSIE, any pending IRQ enabled by this BASEPRI update may
* execute a couple of instructions after the MSR instruction. This has been
* confirmed on Cortex-M3. */
#define IRQ_restore(oldpri) write_special(basepri, (oldpri))
static inline uint16_t _rev16(uint16_t x)
{
uint16_t result;
asm volatile ("rev16 %0,%1" : "=r" (result) : "r" (x));
return result;
}
static inline uint32_t _rev32(uint32_t x)
{
uint32_t result;
asm volatile ("rev %0,%1" : "=r" (result) : "r" (x));
return result;
}
static inline uint32_t _rbit32(uint32_t x)
{
uint32_t result;
asm volatile ("rbit %0,%1" : "=r" (result) : "r" (x));
return result;
}
extern void __bad_cmpxchg(volatile void *ptr, int size);
static always_inline unsigned long __cmpxchg(
volatile void *ptr, unsigned long old, unsigned long new, int size)
{
unsigned long oldval, res;
switch (size) {
case 1:
do {
asm volatile(" ldrexb %1,[%2] \n"
" movs %0,#0 \n"
" cmp %1,%3 \n"
" it eq \n"
" strexbeq %0,%4,[%2] \n"
: "=&r" (res), "=&r" (oldval)
: "r" (ptr), "Ir" (old), "r" (new)
: "memory", "cc");
} while (res);
break;
case 2:
do {
asm volatile(" ldrexh %1,[%2] \n"
" movs %0,#0 \n"
" cmp %1,%3 \n"
" it eq \n"
" strexheq %0,%4,[%2] \n"
: "=&r" (res), "=&r" (oldval)
: "r" (ptr), "Ir" (old), "r" (new)
: "memory", "cc");
} while (res);
break;
case 4:
do {
asm volatile(" ldrex %1,[%2] \n"
" movs %0,#0 \n"
" cmp %1,%3 \n"
" it eq \n"
" strexeq %0,%4,[%2] \n"
: "=&r" (res), "=&r" (oldval)
: "r" (ptr), "Ir" (old), "r" (new)
: "memory", "cc");
} while (res);
break;
default:
__bad_cmpxchg(ptr, size);
oldval = 0;
}
return oldval;
}
#define cmpxchg(ptr,o,n) \
((__typeof__(*(ptr)))__cmpxchg((ptr), \
(unsigned long)(o), \
(unsigned long)(n), \
sizeof(*(ptr))))
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

149
inc/stm32f10x.h Normal file
View File

@@ -0,0 +1,149 @@
/*
* stm32f10x.h
*
* Core and peripheral registers.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
/* C pointer types */
#define STK volatile struct stk * const
#define SCB volatile struct scb * const
#define NVIC volatile struct nvic * const
#define FLASH volatile struct flash * const
#define PWR volatile struct pwr * const
#define BKP volatile struct bkp * const
#define RCC volatile struct rcc * const
#define IWDG volatile struct iwdg * const
#define GPIO volatile struct gpio * const
#define AFIO volatile struct afio * const
#define EXTI volatile struct exti * const
#define DMA volatile struct dma * const
#define TIM volatile struct tim * const
#define SPI volatile struct spi * const
#define I2C volatile struct i2c * const
#define USART volatile struct usart * const
#define USB volatile struct usb * const
#define USB_BUFD volatile struct usb_bufd * const
#define USB_BUF volatile uint32_t * const
#define USB_OTG volatile struct usb_otg * const
/* C-accessible registers. */
static STK stk = (struct stk *)STK_BASE;
static SCB scb = (struct scb *)SCB_BASE;
static NVIC nvic = (struct nvic *)NVIC_BASE;
static FLASH flash = (struct flash *)FLASH_BASE;
static PWR pwr = (struct pwr *)PWR_BASE;
static BKP bkp = (struct bkp *)BKP_BASE;
static RCC rcc = (struct rcc *)RCC_BASE;
static IWDG iwdg = (struct iwdg *)IWDG_BASE;
static GPIO gpioa = (struct gpio *)GPIOA_BASE;
static GPIO gpiob = (struct gpio *)GPIOB_BASE;
static GPIO gpioc = (struct gpio *)GPIOC_BASE;
static GPIO gpiod = (struct gpio *)GPIOD_BASE;
static GPIO gpioe = (struct gpio *)GPIOE_BASE;
static GPIO gpiof = (struct gpio *)GPIOF_BASE;
static GPIO gpiog = (struct gpio *)GPIOG_BASE;
static AFIO afio = (struct afio *)AFIO_BASE;
static EXTI exti = (struct exti *)EXTI_BASE;
static DMA dma1 = (struct dma *)DMA1_BASE;
static DMA dma2 = (struct dma *)DMA2_BASE;
static TIM tim1 = (struct tim *)TIM1_BASE;
static TIM tim2 = (struct tim *)TIM2_BASE;
static TIM tim3 = (struct tim *)TIM3_BASE;
static TIM tim4 = (struct tim *)TIM4_BASE;
static TIM tim5 = (struct tim *)TIM5_BASE;
static TIM tim6 = (struct tim *)TIM6_BASE;
static TIM tim7 = (struct tim *)TIM7_BASE;
static SPI spi1 = (struct spi *)SPI1_BASE;
static SPI spi2 = (struct spi *)SPI2_BASE;
static SPI spi3 = (struct spi *)SPI3_BASE;
static I2C i2c1 = (struct i2c *)I2C1_BASE;
static I2C i2c2 = (struct i2c *)I2C2_BASE;
static USART usart1 = (struct usart *)USART1_BASE;
static USART usart2 = (struct usart *)USART2_BASE;
static USART usart3 = (struct usart *)USART3_BASE;
static USB usb = (struct usb *)USB_BASE;
static USB_BUFD usb_bufd = (struct usb_bufd *)USB_BUF_BASE;
static USB_BUF usb_buf = (uint32_t *)USB_BUF_BASE;
static USB_OTG usb_otg = (struct usb_otg *)USB_OTG_BASE;
/* NVIC table */
extern uint32_t vector_table[];
/* System */
void stm32_init(void);
void stm32_bootloader_enter(void);
void system_reset(void);
/* Clocks */
#define SYSCLK_MHZ 72
#define SYSCLK (SYSCLK_MHZ * 1000000)
#define sysclk_ns(x) (((x) * SYSCLK_MHZ) / 1000)
#define sysclk_us(x) ((x) * SYSCLK_MHZ)
#define sysclk_ms(x) ((x) * SYSCLK_MHZ * 1000)
#define sysclk_stk(x) ((x) * (SYSCLK_MHZ / STK_MHZ))
/* SysTick Timer */
#define STK_MHZ (SYSCLK_MHZ / 8)
void delay_ticks(unsigned int ticks);
void delay_ns(unsigned int ns);
void delay_us(unsigned int us);
void delay_ms(unsigned int ms);
typedef uint32_t stk_time_t;
#define stk_now() (stk->val)
#define stk_diff(x,y) (((x)-(y)) & STK_MASK) /* d = y - x */
#define stk_add(x,d) (((x)-(d)) & STK_MASK) /* y = x + d */
#define stk_sub(x,d) (((x)+(d)) & STK_MASK) /* y = x - d */
#define stk_timesince(x) stk_diff(x,stk_now())
#define stk_us(x) ((x) * STK_MHZ)
#define stk_ms(x) stk_us((x) * 1000)
#define stk_sysclk(x) ((x) / (SYSCLK_MHZ / STK_MHZ))
/* NVIC */
#define IRQx_enable(x) do { \
barrier(); \
nvic->iser[(x)>>5] = 1u<<((x)&31); \
} while (0)
#define IRQx_disable(x) do { \
nvic->icer[(x)>>5] = 1u<<((x)&31); \
cpu_sync(); \
} while (0)
#define IRQx_is_enabled(x) ((nvic->iser[(x)>>5]>>((x)&31))&1)
#define IRQx_set_pending(x) (nvic->ispr[(x)>>5] = 1u<<((x)&31))
#define IRQx_clear_pending(x) (nvic->icpr[(x)>>5] = 1u<<((x)&31))
#define IRQx_is_pending(x) ((nvic->ispr[(x)>>5]>>((x)&31))&1)
#define IRQx_set_prio(x,y) (nvic->ipr[x] = (y) << 4)
#define IRQx_get_prio(x) (nvic->ipr[x] >> 4)
/* GPIO */
void gpio_configure_pin(GPIO gpio, unsigned int pin, unsigned int mode);
#define gpio_write_pin(gpio, pin, level) \
((gpio)->bsrr = ((level) ? 0x1u : 0x10000u) << (pin))
#define gpio_write_pins(gpio, mask, level) \
((gpio)->bsrr = (uint32_t)(mask) << ((level) ? 0 : 16))
#define gpio_read_pin(gpio, pin) (((gpio)->idr >> (pin)) & 1)
bool_t gpio_pins_connected(GPIO gpio1, unsigned int pin1,
GPIO gpio2, unsigned int pin2);
/* FPEC */
void fpec_init(void);
void fpec_page_erase(uint32_t flash_address);
void fpec_write(const void *data, unsigned int size, uint32_t flash_address);
#define FLASH_PAGE_SIZE 1024
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

1007
inc/stm32f10x_regs.h Normal file
View File

File diff suppressed because it is too large Load Diff

37
inc/time.h Normal file
View File

@@ -0,0 +1,37 @@
/*
* time.h
*
* System-time abstraction over STM32 STK timer.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
typedef uint32_t time_t;
#define TIME_MHZ STK_MHZ
#define time_us(x) stk_us(x)
#define time_ms(x) stk_ms(x)
#define time_sysclk(x) stk_sysclk(x)
#define sysclk_time(x) sysclk_stk(x)
time_t time_now(void);
#define time_diff(x,y) ((int32_t)((y)-(x))) /* d = y - x */
#define time_add(x,d) ((time_t)((x)+(d))) /* y = x + d */
#define time_sub(x,d) ((time_t)((x)-(d))) /* y = x - d */
#define time_since(x) time_diff(x, time_now())
void time_init(void);
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

34
inc/timer.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* timer.h
*
* Deadline-based timer callbacks.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
struct timer {
time_t deadline;
void (*cb_fn)(void *);
void *cb_dat;
struct timer *next;
};
/* Safe to call from any priority level same or lower than TIMER_IRQ_PRI. */
void timer_init(struct timer *timer, void (*cb_fn)(void *), void *cb_dat);
void timer_set(struct timer *timer, time_t deadline);
void timer_cancel(struct timer *timer);
void timers_init(void);
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

164
inc/util.h Normal file
View File

@@ -0,0 +1,164 @@
/*
* util.h
*
* Utility definitions.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
#define FW_VER "0.0.1a"
#ifndef NDEBUG
#define ASSERT(p) do { if (!(p)) illegal(); } while (0)
#else
#define ASSERT(p) do { if (0 && (p)) {} } while (0)
#endif
typedef char bool_t;
#define TRUE 1
#define FALSE 0
#define LONG_MAX ((long int)((~0UL)>>1))
#define LONG_MIN ((long int)~LONG_MAX)
#ifndef offsetof
#define offsetof(a,b) __builtin_offsetof(a,b)
#endif
#define container_of(ptr, type, member) ({ \
typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
#define max(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x > _y ? _x : _y; })
#define min_t(type,x,y) \
({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
#define max_t(type,x,y) \
({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
/* Fast memset/memcpy: Pointers must be word-aligned, count must be a non-zero
* multiple of 32 bytes. */
void memset_fast(void *s, int c, size_t n);
void memcpy_fast(void *dest, const void *src, size_t n);
void *memset(void *s, int c, size_t n);
void *memcpy(void *dest, const void *src, size_t n);
void *memmove(void *dest, const void *src, size_t n);
int memcmp(const void *s1, const void *s2, size_t n);
size_t strlen(const char *s);
size_t strnlen(const char *s, size_t maxlen);
int strcmp(const char *s1, const char *s2);
int strncmp(const char *s1, const char *s2, size_t n);
char *strrchr(const char *s, int c);
int tolower(int c);
int isspace(int c);
long int strtol(const char *nptr, char **endptr, int base);
int vsnprintf(char *str, size_t size, const char *format, va_list ap)
__attribute__ ((format (printf, 3, 0)));
int snprintf(char *str, size_t size, const char *format, ...)
__attribute__ ((format (printf, 3, 4)));
#define le16toh(x) (x)
#define le32toh(x) (x)
#define htole16(x) (x)
#define htole32(x) (x)
#define be16toh(x) _rev16(x)
#define be32toh(x) _rev32(x)
#define htobe16(x) _rev16(x)
#define htobe32(x) _rev32(x)
/* Arena-based memory allocation */
void *arena_alloc(uint32_t sz);
uint32_t arena_total(void);
uint32_t arena_avail(void);
void arena_init(void);
/* Board-specific callouts */
void board_init(void);
extern uint8_t board_id;
#ifndef NDEBUG
/* Serial console control */
void console_init(void);
void console_sync(void);
void console_crash_on_input(void);
/* Serial console output */
int vprintk(const char *format, va_list ap)
__attribute__ ((format (printf, 1, 0)));
int printk(const char *format, ...)
__attribute__ ((format (printf, 1, 2)));
#else /* NDEBUG */
#define console_init() ((void)0)
#define console_sync() IRQ_global_disable()
#define console_crash_on_input() ((void)0)
static inline int vprintk(const char *format, va_list ap) { return 0; }
static inline int printk(const char *format, ...) { return 0; }
#endif
/* USB */
void usb_init(void);
void usb_process(void);
int ep_rx_ready(uint8_t ep);
void usb_read(uint8_t ep, void *buf, uint32_t len);
bool_t ep_tx_ready(uint8_t ep);
void usb_write(uint8_t ep, const void *buf, uint32_t len);
/* Floppy */
void floppy_init(void);
void floppy_reset(void);
void floppy_configured(void);
void floppy_process(void);
/* CRC-CCITT */
uint16_t crc16_ccitt(const void *buf, size_t len, uint16_t crc);
/* Text/data/BSS address ranges. */
extern char _stext[], _etext[];
extern char _sdat[], _edat[], _ldat[];
extern char _sbss[], _ebss[];
/* Stacks. */
extern uint32_t _thread_stacktop[], _thread_stackbottom[];
extern uint32_t _irq_stacktop[], _irq_stackbottom[];
/* Default exception handler. */
void EXC_unused(void);
/* IRQ priorities, 0 (highest) to 15 (lowest). */
#define RESET_IRQ_PRI 0
#define INDEX_IRQ_PRI 2
#define TIMER_IRQ_PRI 4
#define USB_IRQ_PRI 14
#define CONSOLE_IRQ_PRI 15
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

View File

@@ -0,0 +1,26 @@
# UDEV Rules for Greaseweazle
#
# This file must be placed at:
#
# /etc/udev/rules.d/49-greaseweazle.rules (preferred location)
# or
# /lib/udev/rules.d/49-greaseweazle.rules (req'd on some broken systems)
#
# To install, type this command in a terminal:
# sudo cp 49-greaseweazle.rules /etc/udev/rules.d/49-greaseweazle.rules
#
# After this file is installed, physically unplug and reconnect Greaseweazle.
#
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
ENV{MTP_NO_PROBE}="1"
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
SUBSYSTEMS=="usb", MODE:="0666"
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
KERNEL=="ttyACM*", MODE:="0666"
#
# If you share your linux system with other users, or just don't like the
# idea of write permission for everybody, you can replace MODE:="0666" with
# OWNER:="yourusername" to create the device owned by you, or with
# GROUP:="somegroupname" and mange access using standard unix groups.

20
scripts/foop Normal file
View File

@@ -0,0 +1,20 @@
# UDEV Rules for Greaseweazle
#
# To install, type this command in a terminal:
# sudo cp 49-greaseweazle.rules /etc/udev/rules.d/.
#
# After this file is installed, physically unplug and reconnect Greaseweazle.
#
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
ENV{ID_MM_DEVICE_IGNORE}="1"
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
ENV{MTP_NO_PROBE}="1"
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
SUBSYSTEMS=="usb", MODE:="0666"
ATTRS{manufacturer}=="Keir Fraser", ATTRS{product}=="GreaseWeazle", \
KERNEL=="ttyACM*", MODE:="0666"
#
# If you share your linux system with other users, or just don't like the
# idea of write permission for everybody, you can replace MODE:="0666" with
# OWNER:="yourusername" to create the device owned by you, or with
# GROUP:="somegroupname" and mange access using standard unix groups.

346
scripts/gw.py Normal file
View File

@@ -0,0 +1,346 @@
# gw.py
#
# Greaseweazle control script.
#
# Written & released by Keir Fraser <keir.xen@gmail.com>
#
# This is free and unencumbered software released into the public domain.
# See the file COPYING for more details, or visit <http://unlicense.org>.
import sys, struct, argparse, serial, collections
from timeit import default_timer as timer
# 40MHz
scp_freq = 40000000
CMD_GET_INFO = 0
CMD_SEEK = 1
CMD_SIDE = 2
CMD_SET_DELAYS = 3
CMD_GET_DELAYS = 4
CMD_MOTOR = 5
CMD_READ_FLUX = 6
CMD_WRITE_FLUX = 7
CMD_GET_FLUX_STATUS = 8
CMD_GET_READ_INFO = 9
ACK_OKAY = 0
ACK_BAD_COMMAND = 1
ACK_NO_INDEX = 2
ACK_NO_TRK0 = 3
ACK_FLUX_OVERFLOW = 4
ACK_FLUX_UNDERFLOW = 5
ACK_WRPROT = 6
ACK_MAX = 6
ack_str = [
"Okay", "Bad Command", "No Index", "Track 0 not found",
"Flux Overflow", "Flux Underflow", "Disk is Write Protected" ]
class CmdError(Exception):
def __init__(self, code):
self.code = code
def __str__(self):
if self.code <= ACK_MAX:
return ack_str[self.code]
return "Unknown Error (%u)" % self.code
def send_cmd(cmd):
ser.write(cmd)
(c,r) = struct.unpack("2B", ser.read(2))
assert c == cmd[0]
if r != 0:
raise CmdError(r)
def get_fw_info():
send_cmd(struct.pack("3B", CMD_GET_INFO, 3, 0))
x = struct.unpack("<4BI24x", ser.read(32))
return x
def print_fw_info(info):
(major, minor, max_revs, max_cmd, freq) = info
print("Greaseweazle v%u.%u" % (major, minor))
print("Max revs %u" % (max_revs))
print("Max cmd %u" % (max_cmd))
print("Sample frequency: %.2f MHz" % (freq / 1000000))
def seek(cyl, side):
send_cmd(struct.pack("3B", CMD_SEEK, 3, cyl))
send_cmd(struct.pack("3B", CMD_SIDE, 3, side))
def get_delays():
send_cmd(struct.pack("2B", CMD_GET_DELAYS, 2))
return struct.unpack("<4H", ser.read(4*2))
def print_delays(x):
(step_delay, seek_settle, motor_delay, auto_off) = x
print("Step Delay: %ums" % step_delay)
print("Settle Time: %ums" % seek_settle)
print("Motor Delay: %ums" % motor_delay)
print("Auto Off: %ums" % auto_off)
def set_delays(step_delay = None, seek_settle = None,
motor_delay = None, auto_off = None):
(_step_delay, _seek_settle, _motor_delay, _auto_off) = get_delays()
if not step_delay: step_delay = _step_delay
if not seek_settle: seek_settle = _seek_settle
if not motor_delay: motor_delay = _motor_delay
if not auto_off: auto_off = _auto_off
send_cmd(struct.pack("<2B4H", CMD_SET_DELAYS, 10,
step_delay, seek_settle, motor_delay, auto_off))
def motor(state):
send_cmd(struct.pack("3B", CMD_MOTOR, 3, int(state)))
def get_read_info():
send_cmd(struct.pack("2B", CMD_GET_READ_INFO, 2))
x = []
for i in range(7):
x.append(struct.unpack("<2I", ser.read(2*4)))
return x
def print_read_info(info):
for (time, samples) in info:
print("%u ticks, %u samples" % (time, samples))
def write_flux(flux):
start = timer()
x = bytearray()
for val in flux:
if val == 0:
pass
elif val < 250:
x.append(val)
else:
high = val // 250
if high <= 5:
x.append(249+high)
x.append(1 + val%250)
else:
x.append(255)
x.append(1 | (val<<1) & 255)
x.append(1 | (val>>6) & 255)
x.append(1 | (val>>13) & 255)
x.append(1 | (val>>20) & 255)
x.append(0) # End of Stream
end = timer()
#print("%u flux -> %u bytes in %f seconds" % (len(flux), len(x), end-start))
retry = 0
while True:
start = timer()
send_cmd(struct.pack("2B", CMD_WRITE_FLUX, 2))
ser.write(x)
ser.read(1) # Sync with Greaseweazle
try:
send_cmd(struct.pack("2B", CMD_GET_FLUX_STATUS, 2))
except CmdError as error:
if error.code == ACK_FLUX_UNDERFLOW and retry < 5:
retry += 1
print("Retry #%u..." % retry)
continue;
raise
end = timer()
#print("Track written in %f seconds" % (end-start))
break
def read_flux(nr_revs):
retry = 0
while True:
start = timer()
x = collections.deque()
send_cmd(struct.pack("3B", CMD_READ_FLUX, 3, nr_revs))
nr = 0
while True:
x += ser.read(1)
x += ser.read(ser.in_waiting)
nr += 1;
if x[-1] == 0:
break
try:
send_cmd(struct.pack("2B", CMD_GET_FLUX_STATUS, 2))
except CmdError as error:
if error.code == ACK_FLUX_OVERFLOW and retry < 5:
retry += 1
print("Retry #%u..." % retry)
del x
continue;
raise
end = timer()
break
#print("Read %u bytes in %u batches in %f seconds" % (len(x), nr, end-start))
start = timer()
y = []
while x:
i = x.popleft()
if i < 250:
y.append(i)
elif i == 255:
val = (x.popleft() & 254) >> 1
val += (x.popleft() & 254) << 6
val += (x.popleft() & 254) << 13
val += (x.popleft() & 254) << 20
y.append(val)
else:
val = (i - 249) * 250
val += x.popleft() - 1
y.append(val)
assert y[-1] == 0
y = y[:-1]
end = timer()
#print("Processed %u flux values in %f seconds" % (len(y), end-start))
return y
def read(args):
factor = scp_freq / sample_freq
trk_dat = bytearray()
trk_offs = []
if args.single_sided:
track_range = range(args.scyl, args.ecyl+1)
nr_sides = 1
else:
track_range = range(args.scyl*2, (args.ecyl+1)*2)
nr_sides = 2
for i in track_range:
cyl = i >> (nr_sides - 1)
side = i & (nr_sides - 1)
print("\rReading Track %u.%u..." % (cyl, side), end="")
trk_offs.append(len(trk_dat))
seek(cyl, side)
flux = read_flux(args.revs)
info = get_read_info()[:args.revs]
#print_read_info(info)
trk_dat += struct.pack("<3sB", b"TRK", i)
dat_off = 4 + args.revs*12
for (time, samples) in info:
time = int(round(time * factor))
trk_dat += struct.pack("<III", time, samples, dat_off)
dat_off += samples * 2
rem = 0.0
for x in flux:
y = x * factor + rem
val = int(round(y))
rem = y - val
while val >= 65536:
trk_dat.append(0)
trk_dat.append(0)
val -= 65536
if val == 0:
val = 1
trk_dat.append(val>>8)
trk_dat.append(val&255)
print()
csum = 0
for x in trk_dat:
csum += x
trk_offs_dat = bytearray()
for x in trk_offs:
trk_offs_dat += struct.pack("<I", 0x2b0 + x)
trk_offs_dat += bytes(0x2a0 - len(trk_offs_dat))
for x in trk_offs_dat:
csum += x
ds_flag = 0
if args.single_sided:
ds_flag = 1
header_dat = struct.pack("<3s9BI",
b"SCP", # Signature
0, # Version
0x80, # DiskType = Other
args.revs, # Nr Revolutions
args.scyl, # Start track
args.ecyl, # End track
0x21, # Flags = Index, Footer
0, # 16-bit cell width
ds_flag, # Double Sided
0, # 25ns capture
csum & 0xffffffff)
with open(args.file, "wb") as f:
f.write(header_dat)
f.write(trk_offs_dat)
f.write(trk_dat)
def write(args):
factor = sample_freq / scp_freq
with open(args.file, "rb") as f:
dat = f.read()
header = struct.unpack("<3s9BI", dat[0:16])
assert header[0] == b"SCP"
trk_offs = struct.unpack("<168I", dat[16:0x2b0])
if args.single_sided:
track_range = range(args.scyl, args.ecyl+1)
nr_sides = 1
else:
track_range = range(args.scyl*2, (args.ecyl+1)*2)
nr_sides = 2
for i in track_range:
cyl = i >> (nr_sides - 1)
side = i & (nr_sides - 1)
print("\rWriting Track %u.%u..." % (cyl, side), end="")
if trk_offs[i] == 0:
continue
seek(cyl, side)
thdr = struct.unpack("<3sBIII", dat[trk_offs[i]:trk_offs[i]+16])
(sig,_,_,samples,off) = thdr
assert sig == b"TRK"
tdat = dat[trk_offs[i]+off:trk_offs[i]+off+samples*2]
flux = []
rem = 0.0
for i in range(0,len(tdat),2):
x = tdat[i]*256 + tdat[i+1]
if x == 0:
rem += 65536.0
continue
y = x * factor + rem
val = int(round(y))
rem = y - val
flux.append(val)
write_flux(flux)
print()
def main(argv):
actions = {
"read" : read,
"write" : write
}
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("action")
parser.add_argument("--revs", type=int, default=3,
help="number of revolutions to read per track")
parser.add_argument("--scyl", type=int, default=0,
help="first cylinder to read/write")
parser.add_argument("--ecyl", type=int, default=81,
help="last cylinder to read/write")
parser.add_argument("--single-sided", action="store_true")
# parser.add_argument("--total", type=float, default=8.0,
# help="total length, seconds")
parser.add_argument("file", help="in/out filename")
args = parser.parse_args(argv[1:])
global ser
ser = serial.Serial("/dev/ttyACM0")
ser.send_break()
ser.reset_input_buffer()
global sample_freq
info = get_fw_info()
#print_fw_info(info)
sample_freq = info[4]
#set_delays(step_delay=3)
#print_delays(get_delays())
actions[args.action](args)
motor(False)
if __name__ == "__main__":
try:
main(sys.argv)
except CmdError as error:
print("Command Failed: %s" % error)

56
src/Greaseweazle.ld.S Normal file
View File

@@ -0,0 +1,56 @@
ENTRY(vector_table)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
REGION_ALIAS("RO", FLASH);
REGION_ALIAS("RW", RAM);
SECTIONS
{
.text : {
_stext = .;
*(.vector_table)
*(.text)
*(.text*)
*(.rodata)
*(.rodata*)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .;
} >RO
.data : AT (_etext) {
. = ALIGN(4);
_sdat = .;
*(.data)
*(.data*)
. = ALIGN(4);
_edat = .;
_ldat = LOADADDR(.data);
} >RW
.bss : {
. = ALIGN(8);
_irq_stackbottom = .;
. = . + 512;
_irq_stacktop = .;
_thread_stackbottom = .;
. = . + 1024;
_thread_stacktop = .;
_sbss = .;
*(.bss)
*(.bss*)
. = ALIGN(4);
_ebss = .;
} >RW
/DISCARD/ : {
*(.eh_frame)
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}

16
src/Makefile Normal file
View File

@@ -0,0 +1,16 @@
OBJS += arena.o
OBJS += board.o
OBJS += cancellation.o
OBJS += crc.o
OBJS += vectors.o
OBJS += main.o
OBJS += string.o
OBJS += stm32f10x.o
OBJS += time.o
OBJS += timer.o
OBJS += util.o
OBJS += floppy.o
OBJS-$(debug) += console.o
SUBDIRS += usb

51
src/arena.c Normal file
View File

@@ -0,0 +1,51 @@
/*
* arena.c
*
* Arena-based memory allocation. Only one arena, for now.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
#define ram_kb 20
#define ram_bytes (ram_kb*1024)
#define heap_bot (_ebss)
#define heap_top ((char *)0x20000000 + ram_bytes)
static char *heap_p;
void *arena_alloc(uint32_t sz)
{
void *p = heap_p;
heap_p += (sz + 3) & ~3;
ASSERT(heap_p <= heap_top);
return p;
}
uint32_t arena_total(void)
{
return heap_top - heap_bot;
}
uint32_t arena_avail(void)
{
return heap_top - heap_p;
}
void arena_init(void)
{
heap_p = heap_bot;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

54
src/board.c Normal file
View File

@@ -0,0 +1,54 @@
/*
* gotek/board.c
*
* Gotek board-specific setup and management.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
uint8_t board_id;
/* Pull up currently unused and possibly-floating pins. */
static void gpio_pull_up_pins(GPIO gpio, uint16_t mask)
{
unsigned int i;
for (i = 0; i < 16; i++) {
if (mask & 1)
gpio_configure_pin(gpio, i, GPI_pull_up);
mask >>= 1;
}
}
void board_init(void)
{
uint16_t pa_skip, pb_skip;
/* PA0-7 (floppy outputs), PA9-10 (serial console), PA11-12 (USB) */
pa_skip = 0x1eff;
/* PB0 (USB disconnect), PB4,6,8,13 (floppy inputs). */
pb_skip = 0x2151;
/* Pull up all PCx pins. */
gpio_pull_up_pins(gpioc, ~0x0000);
/* Wait for ID to stabilise at PC[15:12]. */
delay_us(5);
board_id = (gpioc->idr >> 12) & 0xf;
gpio_pull_up_pins(gpioa, ~pa_skip);
gpio_pull_up_pins(gpiob, ~pb_skip);
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

86
src/cancellation.c Normal file
View File

@@ -0,0 +1,86 @@
/*
* cancellation.c
*
* Asynchronously-cancellable function calls.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
asm (
".global call_cancellable_fn\n"
".thumb_func \n"
"call_cancellable_fn:\n"
" stmdb.w sp!, {r0, r4, r5, r6, r7, r8, r9, r10, r11, lr}\n"
" str sp, [r0]\n" /* c->sp = PSP */
" mov r0, r2\n" /* r0 = arg */
" blx r1\n" /* (*fn)(arg) */
" ldr r2, [sp]\n"
" movs r1, #0\n"
" str r1, [r2]\n" /* c->sp = NULL */
"do_cancel:\n"
" add sp, #4\n"
" ldmia.w sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc}\n"
);
void do_cancel(void);
/* An exception context for cancel_call(), when initially called from Thread
* context. */
void EXC_sv_call(void) __attribute__((alias("EXC_do_cancel")));
static struct cancellation *exc_cancel;
static void EXC_do_cancel(void)
{
cancel_call(exc_cancel);
exc_cancel = NULL;
}
void cancel_call(struct cancellation *c)
{
struct exception_frame *frame;
uint32_t *new_frame;
/* Bail if the cancellable context is inactive/cancelled. */
if (c->sp == NULL)
return;
/* Switch to exception context if we are not there already. */
if (!in_exception()) {
exc_cancel = c;
sv_call(0);
ASSERT(0); /* unreachable */
}
/* Modify return frame: Jump to exit of call_cancellable_fn() with
* return code -1 and clean xPSR. */
frame = (struct exception_frame *)read_special(psp);
frame->r0 = -1;
frame->pc = (uint32_t)do_cancel;
frame->psr &= 1u<<24; /* Preserve Thumb mode; clear everything else */
/* Find new frame address, set STKALIGN if misaligned. */
new_frame = c->sp - 8;
if ((uint32_t)new_frame & 4) {
new_frame--;
frame->psr |= 1u<<9;
}
/* Copy the stack frame and update Process SP. */
memmove(new_frame, frame, 32);
write_special(psp, new_frame);
/* Do this work at most once per invocation of call_cancellable_fn. */
c->sp = NULL;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

168
src/console.c Normal file
View File

@@ -0,0 +1,168 @@
/*
* console.c
*
* printf-style interface to USART1.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
#define BAUD 3000000 /* 3Mbaud */
#define DMA1_CH4_IRQ 14
void IRQ_14(void) __attribute__((alias("IRQ_dma1_ch4_tc")));
#define USART1_IRQ 37
/* We stage serial output in a ring buffer. DMA occurs from the ring buffer;
* the consumer index being updated each time a DMA sequence completes. */
static char ring[2048];
#define MASK(x) ((x)&(sizeof(ring)-1))
static unsigned int cons, prod, dma_sz;
/* The console can be set into synchronous mode in which case DMA is disabled
* and the transmit-empty flag is polled manually for each byte. */
static bool_t sync_console;
static void kick_tx(void)
{
if (sync_console) {
while (cons != prod) {
while (!(usart1->sr & USART_SR_TXE))
cpu_relax();
usart1->dr = ring[MASK(cons++)];
}
} else if (!dma_sz && (cons != prod)) {
dma_sz = min(MASK(prod-cons), sizeof(ring)-MASK(cons));
dma1->ch4.cmar = (uint32_t)(unsigned long)&ring[MASK(cons)];
dma1->ch4.cndtr = dma_sz;
dma1->ch4.ccr = (DMA_CCR_MSIZE_8BIT |
/* The manual doesn't allow byte accesses to usart. */
DMA_CCR_PSIZE_16BIT |
DMA_CCR_MINC |
DMA_CCR_DIR_M2P |
DMA_CCR_TCIE |
DMA_CCR_EN);
}
}
static void IRQ_dma1_ch4_tc(void)
{
/* Clear the DMA controller. */
dma1->ch4.ccr = 0;
dma1->ifcr = DMA_IFCR_CGIF(4);
/* Update ring state. */
cons += dma_sz;
dma_sz = 0;
/* Kick off more transmit activity. */
kick_tx();
}
int vprintk(const char *format, va_list ap)
{
static char str[128];
char *p, c;
int n;
IRQ_global_disable();
n = vsnprintf(str, sizeof(str), format, ap);
p = str;
while (((c = *p++) != '\0') && ((prod-cons) != (sizeof(ring) - 1))) {
switch (c) {
case '\r': /* CR: ignore as we generate our own CR/LF */
break;
case '\n': /* LF: convert to CR/LF (usual terminal behaviour) */
ring[MASK(prod++)] = '\r';
/* fall through */
default:
ring[MASK(prod++)] = c;
break;
}
}
kick_tx();
IRQ_global_enable();
return n;
}
int printk(const char *format, ...)
{
va_list ap;
int n;
va_start(ap, format);
n = vprintk(format, ap);
va_end(ap);
return n;
}
void console_sync(void)
{
if (sync_console)
return;
IRQ_global_disable();
sync_console = TRUE;
/* Wait for DMA completion and then kick off synchronous mode. */
while (dma1->ch4.cndtr)
cpu_relax();
IRQ_dma1_ch4_tc();
/* Leave IRQs globally disabled. */
}
void console_init(void)
{
/* Turn on the clocks. */
rcc->apb2enr |= RCC_APB2ENR_USART1EN;
/* Enable TX pin (PA9) for USART output, RX pin (PA10) as input. */
gpio_configure_pin(gpioa, 9, AFO_pushpull(_10MHz));
gpio_configure_pin(gpioa, 10, GPI_pull_up);
/* BAUD, 8n1. */
usart1->brr = SYSCLK / BAUD;
usart1->cr1 = (USART_CR1_UE | USART_CR1_TE | USART_CR1_RE);
usart1->cr3 = USART_CR3_DMAT;
/* Initialise DMA1 channel 4 and its completion interrupt. */
dma1->ch4.cpar = (uint32_t)(unsigned long)&usart1->dr;
dma1->ifcr = DMA_IFCR_CGIF(4);
IRQx_set_prio(DMA1_CH4_IRQ, CONSOLE_IRQ_PRI);
IRQx_enable(DMA1_CH4_IRQ);
}
/* Debug helper: if we get stuck somewhere, calling this beforehand will cause
* any serial input to cause a crash dump of the stuck context. */
void console_crash_on_input(void)
{
(void)usart1->dr; /* clear UART_SR_RXNE */
usart1->cr1 |= USART_CR1_RXNEIE;
IRQx_set_prio(USART1_IRQ, RESET_IRQ_PRI);
IRQx_enable(USART1_IRQ);
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

64
src/crc.c Normal file
View File

@@ -0,0 +1,64 @@
/*
* crc.c
*
* Table-based CRC16-CCITT.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
static const uint16_t crc16tab[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
uint16_t crc16_ccitt(const void *buf, size_t len, uint16_t crc)
{
unsigned int i;
const uint8_t *b = buf;
for (i = 0; i < len; i++)
crc = crc16tab[(crc>>8)^*b++] ^ (crc<<8);
return crc;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

940
src/floppy.c Normal file
View File

@@ -0,0 +1,940 @@
/*
* floppy.c
*
* Floppy interface control.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
#define O_FALSE 1
#define O_TRUE 0
#define GPO_bus GPO_opendrain(_2MHz,O_FALSE)
#define AFO_bus (AFO_opendrain(_2MHz) | (O_FALSE<<4))
#define m(bitnr) (1u<<(bitnr))
#define gpio_floppy gpiob
/* Input pins */
#define pin_index 6 /* PB6 */
#define pin_trk0 7 /* PB7 */
#define pin_wrprot 8 /* PB8 */
#define get_index() gpio_read_pin(gpio_floppy, pin_index)
#define get_trk0() gpio_read_pin(gpio_floppy, pin_trk0)
#define get_wrprot() gpio_read_pin(gpio_floppy, pin_wrprot)
/* Output pins. */
#define pin_densel 9 /* PB9 */
#define pin_sel0 10 /* PB10 */
#define pin_motor 11 /* PB11 */
#define pin_dir 12 /* PB12 */
#define pin_step 13 /* PB13 */
#define pin_wgate 14 /* PB14 */
#define pin_side 15 /* PB15 */
/* Track and modify states of output pins. */
static struct {
bool_t densel;
bool_t sel0;
bool_t motor;
bool_t dir;
bool_t step;
bool_t wgate;
bool_t side;
} pins;
#define read_pin(pin) pins.pin
#define write_pin(pin, level) ({ \
gpio_write_pin(gpio_floppy, pin_##pin, level ? O_TRUE : O_FALSE); \
pins.pin = level; })
#define gpio_data gpiob
#define pin_rdata 3
#define tim_rdata (tim2)
#define dma_rdata (dma1->ch7)
#define pin_wdata 4
#define tim_wdata (tim3)
#define dma_wdata (dma1->ch3)
#define irq_index 23
void IRQ_23(void) __attribute__((alias("IRQ_INDEX_changed"))); /* EXTI9_5 */
static volatile struct index {
unsigned int count;
time_t timestamp;
time_t duration;
unsigned int read_prod;
} index;
/* A DMA buffer for running a timer associated with a floppy-data I/O pin. */
static struct dma_ring {
/* Indexes into the buf[] ring buffer. */
uint16_t cons; /* dma_rd: our consumer index for flux samples */
union {
uint16_t prod; /* dma_wr: our producer index for flux samples */
uint16_t prev_sample; /* dma_rd: previous CCRx sample value */
};
/* DMA ring buffer of timer values (ARR or CCRx). */
uint16_t buf[512];
} dma;
static enum {
ST_inactive,
ST_command_wait,
ST_read_flux_wait_index,
ST_read_flux,
ST_read_flux_drain,
ST_write_flux_wait_data,
ST_write_flux_wait_index,
ST_write_flux,
ST_write_flux_drain,
} floppy_state = ST_inactive;
#define FLOPPY_EP 2
#define FLOPPY_MPS 64
static uint8_t u_buf[8192];
static uint32_t u_cons, u_prod;
#define U_MASK(x) ((x)&(sizeof(u_buf)-1))
static struct delay_params {
uint16_t step_delay;
uint16_t seek_settle;
uint16_t motor_delay;
uint16_t auto_off;
} delay_params = {
.step_delay = 3,
.seek_settle = 15,
.motor_delay = 750,
.auto_off = 3000
};
static void step_one_out(void)
{
write_pin(dir, FALSE);
delay_us(10);
write_pin(step, TRUE);
delay_us(10);
write_pin(step, FALSE);
delay_ms(delay_params.step_delay);
}
static void step_one_in(void)
{
write_pin(dir, TRUE);
delay_us(10);
write_pin(step, TRUE);
delay_us(10);
write_pin(step, FALSE);
delay_ms(delay_params.step_delay);
}
static int cur_cyl = -1;
static void drive_select(void)
{
write_pin(sel0, TRUE);
delay_us(10);
}
static void drive_deselect(void)
{
delay_us(10);
write_pin(sel0, FALSE);
}
static bool_t floppy_seek(unsigned int cyl)
{
drive_select();
if ((cyl == 0) || (cur_cyl < 0)) {
unsigned int i;
for (i = 0; i < 256; i++) {
if (get_trk0() == LOW)
break;
step_one_out();
}
cur_cyl = 0;
if (get_trk0() == HIGH) {
drive_deselect();
cur_cyl = -1;
return FALSE;
}
}
if (cur_cyl < 0) {
} else if (cur_cyl <= cyl) {
unsigned int nr = cyl - cur_cyl;
while (nr--)
step_one_in();
} else {
unsigned int nr = cur_cyl - cyl;
while (nr--)
step_one_out();
}
drive_deselect();
delay_ms(delay_params.seek_settle);
cur_cyl = cyl;
return TRUE;
}
static void floppy_motor(bool_t on)
{
if (read_pin(motor) == on)
return;
write_pin(motor, on);
if (on)
delay_ms(delay_params.motor_delay);
}
static void floppy_flux_end(void)
{
/* Turn off timers. */
tim_rdata->ccer = 0;
tim_rdata->cr1 = 0;
tim_rdata->sr = 0; /* dummy, drains any pending DMA */
tim_wdata->cr1 = 0;
tim_wdata->sr = 0; /* dummy, drains any pending DMA */
/* Turn off DMA. */
dma_rdata.ccr = 0;
dma_wdata.ccr = 0;
/* Turn off write pins. */
write_pin(wgate, FALSE);
gpio_configure_pin(gpio_data, pin_wdata, GPO_bus);
}
void floppy_reset(void)
{
unsigned int i;
floppy_state = ST_inactive;
floppy_flux_end();
/* Turn off all output pins. */
for (i = 9; i <= 15; i++)
gpio_write_pin(gpio_floppy, i, O_FALSE);
memset(&pins, 0, sizeof(pins));
}
void floppy_init(void)
{
unsigned int i, GPI_bus;
/* Output pins, unbuffered. */
for (i = 9; i <= 15; i++)
gpio_configure_pin(gpio_floppy, i, GPO_bus);
gpio_configure_pin(gpio_floppy, pin_index, GPI_pull_down);
delay_us(10);
GPI_bus = (get_index() == LOW) ? GPI_pull_up : GPI_floating;
printk("Floppy Inputs: %sternal Pullup\n",
(GPI_bus == GPI_pull_up) ? "In" : "Ex");
/* Input pins. */
for (i = 6; i <= 8; i++)
gpio_configure_pin(gpio_floppy, i, GPI_bus);
/* RDATA/WDATA */
gpio_configure_pin(gpio_data, pin_rdata, GPI_bus);
gpio_configure_pin(gpio_data, pin_wdata, GPO_bus);
afio->mapr |= (AFIO_MAPR_TIM2_REMAP_PARTIAL_1
| AFIO_MAPR_TIM3_REMAP_PARTIAL);
/* PB[15:0] -> EXT[15:0] */
afio->exticr1 = afio->exticr2 = afio->exticr3 = afio->exticr4 = 0x1111;
/* Configure INDEX-changed IRQ. */
exti->rtsr = 0;
exti->imr = exti->ftsr = m(pin_index);
IRQx_set_prio(irq_index, INDEX_IRQ_PRI);
IRQx_enable(irq_index);
/* RDATA Timer setup:
* The counter runs from 0x0000-0xFFFF inclusive at full SYSCLK rate.
*
* Ch.2 (RDATA) is in Input Capture mode, sampling on every clock and with
* no input prescaling or filtering. Samples are captured on the falling
* edge of the input (CCxP=1). DMA is used to copy the sample into a ring
* buffer for batch processing in the DMA-completion ISR. */
tim_rdata->psc = 0;
tim_rdata->arr = 0xffff;
tim_rdata->ccmr1 = TIM_CCMR1_CC2S(TIM_CCS_INPUT_TI1);
tim_rdata->dier = TIM_DIER_CC2DE;
tim_rdata->cr2 = 0;
/* RDATA DMA setup: From the RDATA Timer's CCRx into a circular buffer. */
dma_rdata.cpar = (uint32_t)(unsigned long)&tim_rdata->ccr2;
dma_rdata.cmar = (uint32_t)(unsigned long)dma.buf;
/* WDATA Timer setup:
* The counter is incremented at full SYSCLK rate.
*
* Ch.1 (WDATA) is in PWM mode 1. It outputs O_TRUE for 400ns and then
* O_FALSE until the counter reloads. By changing the ARR via DMA we alter
* the time between (fixed-width) O_TRUE pulses, mimicking floppy drive
* timings. */
tim_wdata->psc = 0;
tim_wdata->ccmr1 = (TIM_CCMR1_CC1S(TIM_CCS_OUTPUT) |
TIM_CCMR1_OC1M(TIM_OCM_PWM1));
tim_wdata->ccer = TIM_CCER_CC1E | ((O_TRUE==0) ? TIM_CCER_CC1P : 0);
tim_wdata->ccr1 = sysclk_ns(400);
tim_wdata->dier = TIM_DIER_UDE;
tim_wdata->cr2 = 0;
/* WDATA DMA setup: From a circular buffer into the WDATA Timer's ARR. */
dma_wdata.cpar = (uint32_t)(unsigned long)&tim_wdata->arr;
dma_wdata.cmar = (uint32_t)(unsigned long)dma.buf;
}
/* CMD_GET_INFO, length=3, 0. Returns 32 bytes after ACK. */
#define CMD_GET_INFO 0
/* CMD_SEEK, length=3, cyl# */
#define CMD_SEEK 1
/* CMD_SIDE, length=3, side# (0=bottom) */
#define CMD_SIDE 2
/* CMD_SET_DELAYS, length=2+4*2, <delay_params> */
#define CMD_SET_DELAYS 3
/* CMD_GET_DELAYS, length=2. Returns 4*2 bytes after ACK. */
#define CMD_GET_DELAYS 4
/* CMD_MOTOR, length=3, motor_state */
#define CMD_MOTOR 5
/* CMD_READ_FLUX, length=3, #revs. Returns flux readings until EOStream. */
#define CMD_READ_FLUX 6
/* CMD_WRITE_FLUX, length=2. Host follows with flux readings until EOStream. */
#define CMD_WRITE_FLUX 7
/* CMD_GET_FLUX_STATUS, length=2. Last read/write status returned in ACK. */
#define CMD_GET_FLUX_STATUS 8
/* CMD_GET_READ_INFO, length=2. Returns 7*8 bytes after ACK. */
#define CMD_GET_READ_INFO 9
#define ACK_OKAY 0
#define ACK_BAD_COMMAND 1
#define ACK_NO_INDEX 2
#define ACK_NO_TRK0 3
#define ACK_FLUX_OVERFLOW 4
#define ACK_FLUX_UNDERFLOW 5
#define ACK_WRPROT 6
const static struct __packed gw_info {
uint8_t fw_major;
uint8_t fw_minor;
uint8_t max_rev;
uint8_t max_cmd;
uint32_t sample_freq;
} gw_info = {
.fw_major = 0, .fw_minor = 1,
.max_rev = 7, .max_cmd = CMD_GET_READ_INFO,
.sample_freq = SYSCLK_MHZ * 1000000u
};
/*
* READ PATH
*/
static struct {
time_t start;
uint8_t status;
uint8_t rev;
uint8_t nr_revs;
bool_t packet_ready;
bool_t write_finished;
unsigned int packet_len;
unsigned int nr_samples;
uint8_t packet[FLOPPY_MPS];
} rw;
static struct {
uint32_t time;
uint32_t samples;
} read_info[7];
static bool_t rdata_encode_flux(void)
{
const uint16_t buf_mask = ARRAY_SIZE(dma.buf) - 1;
uint16_t cons, prod, prev = dma.prev_sample, curr, next;
unsigned int nr_samples;
/* Find out where the DMA engine's producer index has got to. */
prod = ARRAY_SIZE(dma.buf) - dma_rdata.cndtr;
nr_samples = (prod - dma.cons) & buf_mask;
if (rw.rev != index.count) {
unsigned int final_samples = U_MASK(index.read_prod - dma.cons);
read_info[rw.rev].time = sysclk_stk(index.duration);
read_info[rw.rev].samples = rw.nr_samples + final_samples;
rw.rev++;
nr_samples -= final_samples;
rw.nr_samples = 0;
}
rw.nr_samples += nr_samples;
/* Process the flux timings into the raw bitcell buffer. */
for (cons = dma.cons; cons != prod; cons = (cons+1) & buf_mask) {
next = dma.buf[cons];
curr = next - prev;
if (curr == 0) {
/* 0: Skip. */
} else if (curr < 250) {
/* 1-249: One byte. */
u_buf[U_MASK(u_prod++)] = curr;
} else {
unsigned int high = curr / 250;
if (high <= 5) {
/* 250-1500: Two bytes. */
u_buf[U_MASK(u_prod++)] = 249 + high;
u_buf[U_MASK(u_prod++)] = 1 + (curr % 250);
} else {
/* 1501-(2^28-1): Five bytes */
u_buf[U_MASK(u_prod++)] = 0xff;
u_buf[U_MASK(u_prod++)] = 1 | (curr << 1);
u_buf[U_MASK(u_prod++)] = 1 | (curr >> 6);
u_buf[U_MASK(u_prod++)] = 1 | (curr >> 13);
u_buf[U_MASK(u_prod++)] = 1 | (curr >> 20);
}
}
prev = next;
}
/* Save our progress for next time. */
dma.cons = cons;
dma.prev_sample = prev;
return FALSE;
}
static void floppy_read_prep(unsigned int revs)
{
/* Start DMA. */
dma_rdata.cndtr = ARRAY_SIZE(dma.buf);
dma_rdata.ccr = (DMA_CCR_PL_HIGH |
DMA_CCR_MSIZE_16BIT |
DMA_CCR_PSIZE_16BIT |
DMA_CCR_MINC |
DMA_CCR_CIRC |
DMA_CCR_DIR_P2M |
DMA_CCR_EN);
/* DMA soft state. */
dma.cons = 0;
dma.prev_sample = tim_rdata->cnt;
drive_select();
floppy_motor(TRUE);
index.count = 0;
floppy_state = ST_read_flux_wait_index;
memset(&rw, 0, sizeof(rw));
rw.nr_revs = revs;
rw.start = time_now();
rw.status = ACK_OKAY;
}
static void floppy_read_wait_index(void)
{
if (index.count == 0) {
if (time_since(rw.start) > time_ms(2000)) {
/* Timeout */
printk("NO INDEX\n");
floppy_flux_end();
rw.status = ACK_NO_INDEX;
floppy_state = ST_read_flux_drain;
}
return;
}
/* Start timer. */
tim_rdata->ccer = TIM_CCER_CC2E | TIM_CCER_CC2P;
tim_rdata->cr1 = TIM_CR1_CEN;
index.count = 0;
floppy_state = ST_read_flux;
}
static void make_read_packet(unsigned int n)
{
unsigned int c = U_MASK(u_cons);
unsigned int l = ARRAY_SIZE(u_buf) - c;
if (l < n) {
memcpy(rw.packet, &u_buf[c], l);
memcpy(&rw.packet[l], u_buf, n-l);
} else {
memcpy(rw.packet, &u_buf[c], n);
}
u_cons += n;
rw.packet_ready = TRUE;
}
static void floppy_read(void)
{
unsigned int avail = (uint32_t)(u_prod - u_cons);
if (floppy_state == ST_read_flux) {
if (index.count >= rw.nr_revs) {
floppy_flux_end();
floppy_state = ST_read_flux_drain;
}
rdata_encode_flux();
avail = (uint32_t)(u_prod - u_cons);
} else if ((avail < FLOPPY_MPS)
&& !rw.packet_ready
&& ep_tx_ready(FLOPPY_EP)) {
memset(rw.packet, 0, FLOPPY_MPS);
make_read_packet(avail);
usb_write(FLOPPY_EP, rw.packet, avail+1);
floppy_configured();
drive_deselect();
return; /* FINISHED */
}
if (avail > sizeof(u_buf)) {
/* Overflow */
printk("OVERFLOW %u %u %u %u\n", u_cons, u_prod,
rw.packet_ready, ep_tx_ready(FLOPPY_EP));
floppy_flux_end();
rw.status = ACK_FLUX_OVERFLOW;
floppy_state = ST_read_flux_drain;
u_cons = u_prod = 0;
}
if (!rw.packet_ready && (avail >= FLOPPY_MPS))
make_read_packet(FLOPPY_MPS);
if (rw.packet_ready && ep_tx_ready(FLOPPY_EP)) {
usb_write(FLOPPY_EP, rw.packet, FLOPPY_MPS);
rw.packet_ready = FALSE;
}
}
/*
* WRITE PATH
*/
static unsigned int _wdata_decode_flux(uint16_t *tbuf, unsigned int nr)
{
unsigned int todo = nr;
if (todo == 0)
return 0;
while (u_cons != u_prod) {
uint8_t x = u_buf[U_MASK(u_cons)];
uint32_t val = x;
if (x == 0) {
/* 0: Terminate */
u_cons++;
rw.write_finished = TRUE;
goto out;
} else if (x < 250) {
/* 1-249: One byte */
u_cons++;
} else if (x == 255) {
/* 255: Five bytes */
uint32_t val;
if ((uint32_t)(u_prod - u_cons) < 5)
goto out;
u_cons++;
val = (u_buf[U_MASK(u_cons++)] ) >> 1;
val |= (u_buf[U_MASK(u_cons++)] & 0xfe) << 6;
val |= (u_buf[U_MASK(u_cons++)] & 0xfe) << 13;
val |= (u_buf[U_MASK(u_cons++)] & 0xfe) << 20;
val = val ?: 1; /* Force non-zero */
} else {
/* 250-254: Two bytes */
if ((uint32_t)(u_prod - u_cons) < 2)
goto out;
u_cons++;
val = (x - 249) * 250;
val += u_buf[U_MASK(u_cons++)] - 1;
}
*tbuf++ = val - 1 ?: 1; /* Force non-zero */
if (!--todo)
goto out;
}
out:
return nr - todo;
}
static void wdata_decode_flux(void)
{
const uint16_t buf_mask = ARRAY_SIZE(dma.buf) - 1;
uint16_t nr_to_wrap, nr_to_cons, nr, dmacons;
/* Find out where the DMA engine's consumer index has got to. */
dmacons = ARRAY_SIZE(dma.buf) - dma_wdata.cndtr;
/* Find largest contiguous stretch of ring buffer we can fill. */
nr_to_wrap = ARRAY_SIZE(dma.buf) - dma.prod;
nr_to_cons = (dmacons - dma.prod - 1) & buf_mask;
nr = min(nr_to_wrap, nr_to_cons);
/* Now attempt to fill the contiguous stretch with flux data calculated
* from buffered bitcell data. */
dma.prod += _wdata_decode_flux(&dma.buf[dma.prod], nr);
dma.prod &= buf_mask;
}
static void floppy_process_write_packet(void)
{
int len = ep_rx_ready(FLOPPY_EP);
if ((len >= 0) && !rw.packet_ready) {
usb_read(FLOPPY_EP, rw.packet, len);
rw.packet_ready = TRUE;
rw.packet_len = len;
}
if (rw.packet_ready) {
unsigned int avail = ARRAY_SIZE(u_buf) - (uint32_t)(u_prod - u_cons);
unsigned int n = rw.packet_len;
if (avail >= n) {
unsigned int p = U_MASK(u_prod);
unsigned int l = ARRAY_SIZE(u_buf) - p;
if (l < n) {
memcpy(&u_buf[p], rw.packet, l);
memcpy(u_buf, &rw.packet[l], n-l);
} else {
memcpy(&u_buf[p], rw.packet, n);
}
u_prod += n;
rw.packet_ready = FALSE;
}
}
}
static void floppy_write_prep(void)
{
/* Initialise DMA ring indexes (consumer index is implicit). */
dma_wdata.cndtr = ARRAY_SIZE(dma.buf);
dma.prod = 0;
drive_select();
floppy_motor(TRUE);
floppy_state = ST_write_flux_wait_data;
memset(&rw, 0, sizeof(rw));
rw.status = ACK_OKAY;
if (get_wrprot() == LOW) {
floppy_flux_end();
rw.status = ACK_WRPROT;
floppy_state = ST_write_flux_drain;
}
}
static void floppy_write_wait_data(void)
{
floppy_process_write_packet();
wdata_decode_flux();
/* Wait for DMA and input buffers to fill, or write stream to end. */
if (((dma.prod != (ARRAY_SIZE(dma.buf)-1))
|| ((uint32_t)(u_prod - u_cons) < (ARRAY_SIZE(u_buf) - 512)))
&& !rw.write_finished)
return;
index.count = 0;
floppy_state = ST_write_flux_wait_index;
rw.start = time_now();
/* Enable DMA only after flux values are generated. */
dma_wdata.ccr = (DMA_CCR_PL_HIGH |
DMA_CCR_MSIZE_16BIT |
DMA_CCR_PSIZE_16BIT |
DMA_CCR_MINC |
DMA_CCR_CIRC |
DMA_CCR_DIR_M2P |
DMA_CCR_EN);
}
static void floppy_write_wait_index(void)
{
if (index.count == 0) {
if (time_since(rw.start) > time_ms(2000)) {
/* Timeout */
floppy_flux_end();
rw.status = ACK_NO_INDEX;
floppy_state = ST_write_flux_drain;
}
return;
}
/* Start timer. */
tim_wdata->egr = TIM_EGR_UG;
tim_wdata->sr = 0; /* dummy write, gives h/w time to process EGR.UG=1 */
tim_wdata->cr1 = TIM_CR1_CEN;
/* Enable output. */
gpio_configure_pin(gpio_data, pin_wdata, AFO_bus);
write_pin(wgate, TRUE);
index.count = 0;
floppy_state = ST_write_flux;
}
static void floppy_write_check_underflow(void)
{
uint32_t avail = u_prod - u_cons;
if (/* We've run the input buffer dry. */
(avail == 0)
/* The input buffer is nearly dry, and doesn't contain EOStream. */
|| ((avail < 16) && (u_buf[U_MASK(u_prod-1)] != 0))) {
/* Underflow */
printk("UNDERFLOW %u %u %u %u\n", u_cons, u_prod,
rw.packet_ready, ep_rx_ready(FLOPPY_EP));
floppy_flux_end();
rw.status = ACK_FLUX_UNDERFLOW;
floppy_state = ST_write_flux_drain;
}
}
static void floppy_write(void)
{
uint16_t dmacons, todo, prev_todo;
floppy_process_write_packet();
wdata_decode_flux();
if (!rw.write_finished) {
floppy_write_check_underflow();
return;
}
/* Wait for DMA ring to drain. */
todo = ~0;
do {
/* Early termination on index pulse? */
// if (wr->terminate_at_index && (index.count != index_count))
// goto out;
/* Check progress of draining the DMA ring. */
prev_todo = todo;
dmacons = ARRAY_SIZE(dma.buf) - dma_wdata.cndtr;
todo = (dma.prod - dmacons) & (ARRAY_SIZE(dma.buf) - 1);
} while ((todo != 0) && (todo <= prev_todo));
floppy_flux_end();
floppy_state = ST_write_flux_drain;
}
static void floppy_write_drain(void)
{
/* Drain the write stream. */
if (!rw.write_finished) {
floppy_process_write_packet();
(void)_wdata_decode_flux(dma.buf, ARRAY_SIZE(dma.buf));
return;
}
/* Wait for space to write ACK packet. */
if (!ep_tx_ready(FLOPPY_EP))
return;
/* ACK with Status byte. */
u_buf[0] = rw.status;
usb_write(FLOPPY_EP, u_buf, 1);
/* Reset for next command. */
floppy_configured();
drive_deselect();
}
static void process_command(void)
{
uint8_t cmd = u_buf[0];
uint8_t len = u_buf[1];
uint8_t resp_sz = 2;
switch (cmd) {
case CMD_GET_INFO: {
uint8_t idx = u_buf[2];
if (len != 3) goto bad_command;
if (idx != 0) goto bad_command;
memset(&u_buf[2], 0, 32);
memcpy(&u_buf[2], &gw_info, sizeof(gw_info));
resp_sz += 32;
break;
}
case CMD_SEEK: {
uint8_t cyl = u_buf[2];
if (len != 3) goto bad_command;
if (cyl > 85) goto bad_command;
u_buf[1] = floppy_seek(cyl) ? ACK_OKAY : ACK_NO_TRK0;
goto out;
}
case CMD_SIDE: {
uint8_t side = u_buf[2];
if (len != 3) goto bad_command;
if (side > 1) goto bad_command;
write_pin(side, side);
break;
}
case CMD_SET_DELAYS: {
if (len != (2+sizeof(delay_params))) goto bad_command;
memcpy(&delay_params, &u_buf[2], sizeof(delay_params));
break;
}
case CMD_GET_DELAYS: {
if (len != 2) goto bad_command;
memcpy(&u_buf[2], &delay_params, sizeof(delay_params));
resp_sz += sizeof(delay_params);
break;
}
case CMD_MOTOR: {
uint8_t state = u_buf[2];
if (len != 3) goto bad_command;
if (state > 1) goto bad_command;
floppy_motor(state);
break;
}
case CMD_READ_FLUX: {
uint8_t revs = u_buf[2];
if (len != 3) goto bad_command;
if ((revs == 0) || (revs > 7)) goto bad_command;
floppy_read_prep(revs);
break;
}
case CMD_WRITE_FLUX: {
if (len != 2) goto bad_command;
floppy_write_prep();
break;
}
case CMD_GET_FLUX_STATUS: {
if (len != 2) goto bad_command;
u_buf[1] = rw.status;
goto out;
}
case CMD_GET_READ_INFO: {
if (len != 2) goto bad_command;
memcpy(&u_buf[2], &read_info, sizeof(read_info));
resp_sz += sizeof(read_info);
break;
}
default:
goto bad_command;
}
u_buf[1] = ACK_OKAY;
out:
usb_write(FLOPPY_EP, u_buf, resp_sz);
return;
bad_command:
u_buf[1] = ACK_BAD_COMMAND;
goto out;
}
void floppy_configured(void)
{
floppy_state = ST_command_wait;
u_cons = u_prod = 0;
}
void floppy_process(void)
{
int len;
switch (floppy_state) {
case ST_command_wait:
len = ep_rx_ready(FLOPPY_EP);
if ((len >= 0) && (len < (sizeof(u_buf)-u_prod))) {
usb_read(FLOPPY_EP, &u_buf[u_prod], len);
u_prod += len;
}
if ((u_prod >= 2) && (u_prod >= u_buf[1]) && ep_tx_ready(FLOPPY_EP)) {
/* Process command and reset for next command. */
process_command();
u_cons = u_prod = 0;
}
break;
case ST_read_flux_wait_index:
floppy_read_wait_index();
break;
case ST_read_flux:
case ST_read_flux_drain:
floppy_read();
break;
case ST_write_flux_wait_data:
floppy_write_wait_data();
break;
case ST_write_flux_wait_index:
floppy_write_wait_index();
break;
case ST_write_flux:
floppy_write();
break;
case ST_write_flux_drain:
floppy_write_drain();
break;
default:
break;
}
}
/*
* INTERRUPT HANDLERS
*/
static void IRQ_INDEX_changed(void)
{
time_t now = time_now();
/* Clear INDEX-changed flag. */
exti->pr = m(pin_index);
index.count++;
index.duration = time_diff(index.timestamp, now);
index.timestamp = now;
index.read_prod = ARRAY_SIZE(dma.buf) - dma_rdata.cndtr;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

64
src/main.c Normal file
View File

@@ -0,0 +1,64 @@
/*
* main.c
*
* System initialisation and navigation main loop.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
int EXC_reset(void) __attribute__((alias("main")));
static void canary_init(void)
{
_irq_stackbottom[0] = _thread_stackbottom[0] = 0xdeadbeef;
}
static void canary_check(void)
{
ASSERT(_irq_stackbottom[0] == 0xdeadbeef);
ASSERT(_thread_stackbottom[0] == 0xdeadbeef);
}
int main(void)
{
/* Relocate DATA. Initialise BSS. */
if (_sdat != _ldat)
memcpy(_sdat, _ldat, _edat-_sdat);
memset(_sbss, 0, _ebss-_sbss);
canary_init();
stm32_init();
time_init();
console_init();
console_crash_on_input();
board_init();
delay_ms(200); /* 5v settle */
printk("\n** Greaseweazle v%s\n", FW_VER);
printk("** Keir Fraser <keir.xen@gmail.com>\n");
printk("** https://github.com/keirf/Greaseweazle\n\n");
floppy_init();
usb_init();
for (;;) {
canary_check();
usb_process();
floppy_process();
}
return 0;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

212
src/stm32f10x.c Normal file
View File

@@ -0,0 +1,212 @@
/*
* stm32f10x.c
*
* Core and peripheral registers.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
struct extra_exception_frame {
uint32_t r4, r5, r6, r7, r8, r9, r10, r11, lr;
};
void EXC_unexpected(struct extra_exception_frame *extra)
{
struct exception_frame *frame;
uint8_t exc = (uint8_t)read_special(psr);
uint32_t msp, psp;
if (extra->lr & 4) {
frame = (struct exception_frame *)read_special(psp);
psp = (uint32_t)(frame + 1);
msp = (uint32_t)(extra + 1);
} else {
frame = (struct exception_frame *)(extra + 1);
psp = read_special(psp);
msp = (uint32_t)(frame + 1);
}
printk("Unexpected %s #%u at PC=%08x (%s):\n",
(exc < 16) ? "Exception" : "IRQ",
(exc < 16) ? exc : exc - 16,
frame->pc, (extra->lr & 8) ? "Thread" : "Handler");
printk(" r0: %08x r1: %08x r2: %08x r3: %08x\n",
frame->r0, frame->r1, frame->r2, frame->r3);
printk(" r4: %08x r5: %08x r6: %08x r7: %08x\n",
extra->r4, extra->r5, extra->r6, extra->r7);
printk(" r8: %08x r9: %08x r10: %08x r11: %08x\n",
extra->r8, extra->r9, extra->r10, extra->r11);
printk(" r12: %08x sp: %08x lr: %08x pc: %08x\n",
frame->r12, (extra->lr & 4) ? psp : msp, frame->lr, frame->pc);
printk(" msp: %08x psp: %08x psr: %08x\n",
msp, psp, frame->psr);
system_reset();
}
static void exception_init(void)
{
/* Initialise and switch to Process SP. Explicit asm as must be
* atomic wrt updates to SP. We can't guarantee that in C. */
asm volatile (
" mrs r1,msp \n"
" msr psp,r1 \n" /* Set up Process SP */
" movs r1,%0 \n"
" msr control,r1 \n" /* Switch to Process SP */
" isb \n" /* Flush the pipeline */
:: "i" (CONTROL_SPSEL) : "r1" );
/* Set up Main SP for IRQ/Exception context. */
write_special(msp, _irq_stacktop);
/* Initialise interrupts and exceptions. */
scb->vtor = (uint32_t)(unsigned long)vector_table;
scb->ccr |= SCB_CCR_STKALIGN | SCB_CCR_DIV_0_TRP;
/* GCC inlines memcpy() using full-word load/store regardless of buffer
* alignment. Hence it is unsafe to trap on unaligned accesses. */
/*scb->ccr |= SCB_CCR_UNALIGN_TRP;*/
scb->shcsr |= (SCB_SHCSR_USGFAULTENA |
SCB_SHCSR_BUSFAULTENA |
SCB_SHCSR_MEMFAULTENA);
/* SVCall/PendSV exceptions have lowest priority. */
scb->shpr2 = 0xff<<24;
scb->shpr3 = 0xff<<16;
}
static void clock_init(void)
{
/* Flash controller: reads require 2 wait states at 72MHz. */
flash->acr = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY(2);
/* Start up the external oscillator. */
rcc->cr |= RCC_CR_HSEON;
while (!(rcc->cr & RCC_CR_HSERDY))
cpu_relax();
/* PLLs, scalers, muxes. */
rcc->cfgr = (RCC_CFGR_PLLMUL(9) | /* PLL = 9*8MHz = 72MHz */
RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_ADCPRE_DIV8 |
RCC_CFGR_PPRE1_DIV2);
/* Enable and stabilise the PLL. */
rcc->cr |= RCC_CR_PLLON;
while (!(rcc->cr & RCC_CR_PLLRDY))
cpu_relax();
/* Switch to the externally-driven PLL for system clock. */
rcc->cfgr |= RCC_CFGR_SW_PLL;
while ((rcc->cfgr & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS_PLL)
cpu_relax();
/* Internal oscillator no longer needed. */
rcc->cr &= ~RCC_CR_HSION;
/* Enable SysTick counter at 72/8=9MHz. */
stk->load = STK_MASK;
stk->ctrl = STK_CTRL_ENABLE;
}
static void gpio_init(GPIO gpio)
{
/* Floating Input. Reference Manual states that JTAG pins are in PU/PD
* mode at reset, so ensure all PU/PD are disabled. */
gpio->crl = gpio->crh = 0x44444444u;
}
static void peripheral_init(void)
{
/* Enable basic GPIO and AFIO clocks, all timers, and DMA. */
rcc->apb1enr = (RCC_APB1ENR_TIM2EN |
RCC_APB1ENR_TIM3EN |
RCC_APB1ENR_TIM4EN);
rcc->apb2enr = (RCC_APB2ENR_IOPAEN |
RCC_APB2ENR_IOPBEN |
RCC_APB2ENR_IOPCEN |
RCC_APB2ENR_AFIOEN |
RCC_APB2ENR_TIM1EN);
rcc->ahbenr = RCC_AHBENR_DMA1EN;
/* Turn off serial-wire JTAG and reclaim the GPIOs. */
afio->mapr = AFIO_MAPR_SWJ_CFG_DISABLED;
/* All pins in a stable state. */
gpio_init(gpioa);
gpio_init(gpiob);
gpio_init(gpioc);
}
void stm32_init(void)
{
exception_init();
clock_init();
peripheral_init();
cpu_sync();
}
void delay_ticks(unsigned int ticks)
{
unsigned int diff, cur, prev = stk->val;
for (;;) {
cur = stk->val;
diff = (prev - cur) & STK_MASK;
if (ticks <= diff)
break;
ticks -= diff;
prev = cur;
}
}
void delay_ns(unsigned int ns)
{
delay_ticks((ns * STK_MHZ) / 1000u);
}
void delay_us(unsigned int us)
{
delay_ticks(us * STK_MHZ);
}
void delay_ms(unsigned int ms)
{
delay_ticks(ms * 1000u * STK_MHZ);
}
void gpio_configure_pin(GPIO gpio, unsigned int pin, unsigned int mode)
{
gpio_write_pin(gpio, pin, mode >> 4);
mode &= 0xfu;
if (pin >= 8) {
pin -= 8;
gpio->crh = (gpio->crh & ~(0xfu<<(pin<<2))) | (mode<<(pin<<2));
} else {
gpio->crl = (gpio->crl & ~(0xfu<<(pin<<2))) | (mode<<(pin<<2));
}
}
void system_reset(void)
{
console_sync();
printk("Resetting...\n");
/* Wait for serial console TX to idle. */
while (!(usart1->sr & USART_SR_TXE) || !(usart1->sr & USART_SR_TC))
cpu_relax();
/* Request reset and loop waiting for it to happen. */
scb->aircr = SCB_AIRCR_VECTKEY | SCB_AIRCR_SYSRESETREQ;
for (;;) ;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

178
src/string.c Normal file
View File

@@ -0,0 +1,178 @@
/*
* string.c
*
* String manipulation functions.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
static void do_putch(char **p, char *end, char c)
{
if (*p < end)
**p = c;
(*p)++;
}
int vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
unsigned int x, flags;
int width;
char c, *p = str, *end = p + size - 1, tmp[12], *q;
while ((c = *format++) != '\0') {
if (c != '%') {
do_putch(&p, end, c);
continue;
}
flags = width = 0;
#define BASE (31u << 0)
#define UPPER ( 1u << 8)
#define SIGN ( 1u << 9)
#define ALTERNATE ( 1u << 10)
#define ZEROPAD ( 1u << 11)
#define CHAR ( 1u << 12)
#define SHORT ( 1u << 13)
more:
switch (c = *format++) {
case '*':
width = va_arg(ap, unsigned int);
goto more;
case '#':
flags |= ALTERNATE;
goto more;
case '0':
flags |= ZEROPAD;
goto more;
case '1'...'9':
width = c-'0';
while (((c = *format) >= '0') && (c <= '9')) {
width = width*10 + c-'0';
format++;
}
goto more;
case 'h':
if ((c = *format) == 'h') {
flags |= CHAR;
format++;
} else {
flags |= SHORT;
}
goto more;
case 'o':
flags |= 8;
break;
case 'd':
case 'i':
flags |= SIGN;
case 'u':
flags |= 10;
break;
case 'X':
flags |= UPPER;
case 'x':
case 'p':
flags |= 16;
break;
case 's':
q = va_arg(ap, char *);
while ((c = *q++) != '\0') {
do_putch(&p, end, c);
width--;
}
while (width-- > 0)
do_putch(&p, end, ' ');
continue;
case 'c':
c = va_arg(ap, unsigned int);
default:
do_putch(&p, end, c);
continue;
}
x = va_arg(ap, unsigned int);
if (flags & CHAR) {
if (flags & SIGN)
x = (char)x;
else
x = (unsigned char)x;
} else if (flags & SHORT) {
if (flags & SIGN)
x = (short)x;
else
x = (unsigned short)x;
}
if ((flags & SIGN) && ((int)x < 0)) {
if (flags & ZEROPAD) {
do_putch(&p, end, '-');
flags &= ~SIGN;
}
width--;
x = -x;
} else {
flags &= ~SIGN;
}
if (flags & ALTERNATE) {
if (((flags & BASE) == 8) || ((flags & BASE) == 16)) {
do_putch(&p, end, '0');
width--;
}
if ((flags & BASE) == 16) {
do_putch(&p, end, 'x');
width--;
}
}
if (x == 0) {
q = tmp;
*q++ = '0';
} else {
for (q = tmp; x; q++, x /= (flags&BASE))
*q = ((flags & UPPER)
? "0123456789ABCDEF"
: "0123456789abcdef") [x % (flags&BASE)];
}
while (width-- > (q-tmp))
do_putch(&p, end, (flags & ZEROPAD) ? '0' : ' ');
if (flags & SIGN)
do_putch(&p, end, '-');
while (q != tmp)
do_putch(&p, end, *--q);
};
if (p <= end)
*p = '\0';
else if (str <= end)
*end = '\0';
return p - str;
}
int snprintf(char *str, size_t size, const char *format, ...)
{
va_list ap;
int n;
va_start(ap, format);
n = vsnprintf(str, size, format, ap);
va_end(ap);
return n;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

49
src/time.c Normal file
View File

@@ -0,0 +1,49 @@
/*
* time.c
*
* System-time abstraction over STM32 STK timer.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
static volatile time_t time_stamp;
static struct timer time_stamp_timer;
static void time_stamp_update(void *unused)
{
time_t now = time_now();
time_stamp = ~now;
timer_set(&time_stamp_timer, now + time_ms(500));
}
time_t time_now(void)
{
time_t s, t;
s = time_stamp;
t = stk_now() | (s & (0xff << 24));
if (t > s)
t -= 1u << 24;
return ~t;
}
void time_init(void)
{
timers_init();
time_stamp = stk_now();
timer_init(&time_stamp_timer, time_stamp_update, NULL);
timer_set(&time_stamp_timer, time_now() + time_ms(500));
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

142
src/timer.c Normal file
View File

@@ -0,0 +1,142 @@
/*
* timer.c
*
* Deadline-based timer callbacks.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
/* TIM1_UP: IRQ 25. */
void IRQ_25(void) __attribute__((alias("IRQ_timer")));
#define TIMER_IRQ 25
#define tim tim1
/* IRQ only on counter overflow, one-time enable. */
#define TIM_CR1 (TIM_CR1_URS | TIM_CR1_OPM)
/* Empirically-determined offset applied to timer deadlines to counteract the
* latency incurred by reprogram_timer() and IRQ_timer(). */
#define SLACK_TICKS 12
#define TIMER_INACTIVE ((struct timer *)1ul)
static struct timer *head;
static void reprogram_timer(int32_t delta)
{
tim->cr1 = TIM_CR1;
if (delta < 0x10000) {
/* Fine-grained deadline (sub-microsecond accurate) */
tim->psc = SYSCLK_MHZ/TIME_MHZ-1;
tim->arr = (delta <= SLACK_TICKS) ? 1 : delta-SLACK_TICKS;
} else {
/* Coarse-grained deadline, fires in time to set a shorter,
* fine-grained deadline. */
tim->psc = sysclk_us(100)-1;
tim->arr = min_t(uint32_t, 0xffffu,
delta/time_us(100)-50); /* 5ms early */
}
tim->egr = TIM_EGR_UG; /* update CNT, PSC, ARR */
tim->sr = 0; /* dummy write, gives hardware time to process EGR.UG=1 */
tim->cr1 = TIM_CR1 | TIM_CR1_CEN;
}
void timer_init(struct timer *timer, void (*cb_fn)(void *), void *cb_dat)
{
timer->cb_fn = cb_fn;
timer->cb_dat = cb_dat;
timer->next = TIMER_INACTIVE;
}
static bool_t timer_is_active(struct timer *timer)
{
return timer->next != TIMER_INACTIVE;
}
static void _timer_cancel(struct timer *timer)
{
struct timer *t, **pprev;
if (!timer_is_active(timer))
return;
for (pprev = &head; (t = *pprev) != timer; pprev = &t->next)
continue;
*pprev = t->next;
t->next = TIMER_INACTIVE;
}
void timer_set(struct timer *timer, time_t deadline)
{
struct timer *t, **pprev;
time_t now;
int32_t delta;
uint32_t oldpri;
oldpri = IRQ_save(TIMER_IRQ_PRI);
_timer_cancel(timer);
timer->deadline = deadline;
now = time_now();
delta = time_diff(now, deadline);
for (pprev = &head; (t = *pprev) != NULL; pprev = &t->next)
if (delta <= time_diff(now, t->deadline))
break;
timer->next = *pprev;
*pprev = timer;
if (head == timer)
reprogram_timer(delta);
IRQ_restore(oldpri);
}
void timer_cancel(struct timer *timer)
{
uint32_t oldpri;
oldpri = IRQ_save(TIMER_IRQ_PRI);
_timer_cancel(timer);
IRQ_restore(oldpri);
}
void timers_init(void)
{
tim->cr2 = 0;
tim->dier = TIM_DIER_UIE;
IRQx_set_prio(TIMER_IRQ, TIMER_IRQ_PRI);
IRQx_enable(TIMER_IRQ);
}
static void IRQ_timer(void)
{
struct timer *t;
int32_t delta;
tim->sr = 0;
while ((t = head) != NULL) {
if ((delta = time_diff(time_now(), t->deadline)) > SLACK_TICKS) {
reprogram_timer(delta);
break;
}
head = t->next;
t->next = TIMER_INACTIVE;
(*t->cb_fn)(t->cb_dat);
}
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

6
src/usb/Makefile Normal file
View File

@@ -0,0 +1,6 @@
OBJS += config.o
OBJS += core.o
OBJS += cdc_acm.o
OBJS += hw_f1.o
$(OBJS): CFLAGS += -include defs.h

109
src/usb/cdc_acm.c Normal file
View File

@@ -0,0 +1,109 @@
/*
* cdc_acm.c
*
* USB CDC ACM handling (Communications Device Class, Abstract Control Model).
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
#define TRACE 1
/* CDC Communications Class requests */
#define CDC_SET_LINE_CODING 0x20
#define CDC_GET_LINE_CODING 0x21
#define CDC_SET_CONTROL_LINE_STATE 0x22
#define CDC_SEND_BREAK 0x23
static struct __packed line_coding {
uint32_t baud;
uint8_t nr_stop;
uint8_t parity;
uint8_t nr_data;
} line_coding;
#if TRACE
#define TRC printk
#else
static inline void TRC(const char *format, ...) { }
#endif
static void dump_line_coding(const struct line_coding *lc)
{
int parity = (lc->parity > 4) ? 5 : lc->parity;
TRC("%u,%u%c%s\n", lc->baud, lc->nr_data,
"noems?"[parity],
(lc->nr_stop == 0) ? "1"
: (lc->nr_stop == 1) ? "1.5"
: (lc->nr_stop == 2) ? "2" : "X");
}
bool_t cdc_acm_handle_class_request(void)
{
struct usb_device_request *req = &ep0.req;
bool_t handled = TRUE;
switch (req->bRequest) {
case CDC_SET_LINE_CODING: {
struct line_coding *lc = (struct line_coding *)ep0.data;
TRC("SET_LINE_CODING: ");
dump_line_coding(lc);
line_coding = *lc;
break;
}
case CDC_GET_LINE_CODING: {
struct line_coding *lc = (struct line_coding *)ep0.data;
TRC("GET_LINE_CODING: ");
line_coding = *lc;
dump_line_coding(lc);
ep0.data_len = sizeof(*lc);
break;
}
case CDC_SET_CONTROL_LINE_STATE:
/* wValue = DTR/RTS. We ignore them and return success. */
break;
case CDC_SEND_BREAK:
/* wValue = #millisecs. We ignore it and return success. */
TRC("BREAK\n");
floppy_reset();
floppy_configured();
break;
default:
WARN("[Class-specific: %02x]\n", req->bRequest);
handled = FALSE;
break;
}
return handled;
}
bool_t cdc_acm_set_configuration(void)
{
/* Set up endpoints: XXX Do we need the Notification Element? */
usb_configure_ep(0x81, 0, 0); /* Notification Element (D->H) */
usb_configure_ep(0x02, 0, 64); /* Bulk Pipe (H->D) */
usb_configure_ep(0x82, 0, 64); /* Bulk Pipe (D->H) */
floppy_configured();
return TRUE;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

114
src/usb/config.c Normal file
View File

@@ -0,0 +1,114 @@
/*
* config.c
*
* USB device and configuration descriptors.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
const uint8_t device_descriptor[] __aligned(2) = {
18, /* Length */
DESC_DEVICE, /* Descriptor Type */
0x10,0x01, /* USB 1.1 */
2, 0, 0, /* Class, Subclass, Protocol: CDC */
64, /* Max Packet Size */
0x09,0x12, /* VID = pid.codes Open Source projects */
0x01,0x00, /* PID = Test PID #1 */
0,1, /* Device Release 1.0 */
1,2,0, /* Manufacturer, Product, Serial */
1 /* Number of configurations */
};
const uint8_t config_descriptor[] __aligned(2) = {
0x09, /* 0 bLength */
DESC_CONFIGURATION, /* 1 bDescriptortype - Configuration*/
0x43, 0x00, /* 2 wTotalLength */
0x02, /* 4 bNumInterfaces */
0x01, /* 5 bConfigurationValue */
0x00, /* 6 iConfiguration - index of string */
0x80, /* 7 bmAttributes - Bus powered */
0xC8, /* 8 bMaxPower - 400mA */
/* CDC Communication interface */
0x09, /* 0 bLength */
DESC_INTERFACE, /* 1 bDescriptorType - Interface */
0x00, /* 2 bInterfaceNumber - Interface 0 */
0x00, /* 3 bAlternateSetting */
0x01, /* 4 bNumEndpoints */
2, 2, 1, /* CDC ACM, AT Command Protocol */
0x00, /* 8 iInterface - No string descriptor */
/* Header Functional descriptor */
0x05, /* 0 bLength */
DESC_CS_INTERFACE, /* 1 bDescriptortype, CS_INTERFACE */
0x00, /* 2 bDescriptorsubtype, HEADER */
0x10, 0x01, /* 3 bcdCDC */
/* ACM Functional descriptor */
0x04, /* 0 bLength */
DESC_CS_INTERFACE, /* 1 bDescriptortype, CS_INTERFACE */
0x02, /* 2 bDescriptorsubtype, ABSTRACT CONTROL MANAGEMENT */
0x02, /* 3 bmCapabilities: Supports subset of ACM commands */
/* Union Functional descriptor */
0x05, /* 0 bLength */
DESC_CS_INTERFACE,/* 1 bDescriptortype, CS_INTERFACE */
0x06, /* 2 bDescriptorsubtype, UNION */
0x00, /* 3 bControlInterface - Interface 0 */
0x01, /* 4 bSubordinateInterface0 - Interface 1 */
/* Call Management Functional descriptor */
0x05, /* 0 bLength */
DESC_CS_INTERFACE,/* 1 bDescriptortype, CS_INTERFACE */
0x01, /* 2 bDescriptorsubtype, CALL MANAGEMENT */
0x03, /* 3 bmCapabilities, DIY */
0x01, /* 4 bDataInterface */
/* Notification Endpoint descriptor */
0x07, /* 0 bLength */
DESC_ENDPOINT, /* 1 bDescriptorType */
0x81, /* 2 bEndpointAddress */
0x03, /* 3 bmAttributes */
0x40, /* 4 wMaxPacketSize - Low */
0x00, /* 5 wMaxPacketSize - High */
0xFF, /* 6 bInterval */
/* CDC Data interface */
0x09, /* 0 bLength */
DESC_INTERFACE, /* 1 bDescriptorType */
0x01, /* 2 bInterfacecNumber */
0x00, /* 3 bAlternateSetting */
0x02, /* 4 bNumEndpoints */
USB_CLASS_CDC_DATA, /* 5 bInterfaceClass */
0x00, /* 6 bInterfaceSubClass */
0x00, /* 7 bInterfaceProtocol*/
0x00, /* 8 iInterface - No string descriptor*/
/* Data OUT Endpoint descriptor */
0x07, /* 0 bLength */
DESC_ENDPOINT, /* 1 bDescriptorType */
0x02, /* 2 bEndpointAddress */
0x02, /* 3 bmAttributes */
0x40, /* 4 wMaxPacketSize - Low */
0x00, /* 5 wMaxPacketSize - High */
0x00, /* 6 bInterval */
/* Data IN Endpoint descriptor */
0x07, /* 0 bLength */
DESC_ENDPOINT, /* 1 bDescriptorType */
0x82, /* 2 bEndpointAddress */
0x02, /* 3 bmAttributes */
0x40, /* 4 wMaxPacketSize - Low byte */
0x00, /* 5 wMaxPacketSize - High byte */
0x00 /* 6 bInterval */
};
const char *string_descriptors[] = {
"\x09\x04", /* LANGID: US English */
"Keir Fraser",
"GreaseWeazle",
};
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

100
src/usb/core.c Normal file
View File

@@ -0,0 +1,100 @@
/*
* core.c
*
* USB core.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
struct ep0 ep0;
uint8_t pending_addr;
bool_t handle_control_request(void)
{
struct usb_device_request *req = &ep0.req;
bool_t handled = TRUE;
if (ep0_data_out() && (req->wLength > sizeof(ep0.data))) {
WARN("Ctl OUT too long: %u>%u\n", req->wLength, sizeof(ep0.data));
handled = FALSE;
} else if ((req->bmRequestType == 0x80)
&& (req->bRequest == GET_DESCRIPTOR)) {
uint8_t type = req->wValue >> 8;
uint8_t idx = req->wValue;
if ((type == DESC_DEVICE) && (idx == 0)) {
ep0.data_len = device_descriptor[0]; /* bLength */
memcpy(ep0.data, device_descriptor, ep0.data_len);
} else if ((type == DESC_CONFIGURATION) && (idx == 0)) {
ep0.data_len = config_descriptor[2]; /* wTotalLength */
memcpy(ep0.data, config_descriptor, ep0.data_len);
} else if ((type == DESC_STRING) && (idx < NR_STRING_DESC)) {
const char *s = string_descriptors[idx];
uint16_t *odat = (uint16_t *)ep0.data;
int i = 0;
if (idx == 0) {
odat[i++] = 4+(DESC_STRING<<8);
memcpy(&odat[i++], s, 2);
} else {
odat[i++] = (1+strlen(s))*2+(DESC_STRING<<8);
while (*s)
odat[i++] = *s++;
}
ep0.data_len = i*2;
} else {
WARN("[Unknown descriptor %u,%u]\n", type, idx);
handled = FALSE;
}
} else if ((req->bmRequestType == 0x00)
&& (req->bRequest == SET_ADDRESS)) {
pending_addr = req->wValue & 0x7f;
} else if ((req->bmRequestType == 0x00)
&& (req->bRequest == SET_CONFIGURATION)) {
handled = cdc_acm_set_configuration();
} else if ((req->bmRequestType&0x7f) == 0x21) {
handled = cdc_acm_handle_class_request();
} else {
uint8_t *pkt = (uint8_t *)req;
int i;
WARN("(%02x %02x %02x %02x %02x %02x %02x %02x)",
pkt[0], pkt[1], pkt[2], pkt[3],
pkt[4], pkt[5], pkt[6], pkt[7]);
if (ep0_data_out()) {
WARN("[");
for (i = 0; i < ep0.data_len; i++)
WARN("%02x ", ep0.data[i]);
WARN("]");
}
WARN("\n");
handled = FALSE;
}
if (ep0_data_in() && (ep0.data_len > req->wLength))
ep0.data_len = req->wLength;
return handled;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

85
src/usb/defs.h Normal file
View File

@@ -0,0 +1,85 @@
/*
* defs.h
*
* USB standard definitions and private interfaces.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
/* bRequest: Standard Request Codes */
#define GET_STATUS 0
#define CLEAR_FEATURE 1
#define SET_FEATURE 3
#define SET_ADDRESS 5
#define GET_DESCRIPTOR 6
#define SET_DESCRIPTOR 7
#define GET_CONFIGURATION 8
#define SET_CONFIGURATION 9
#define GET_INTERFACE 10
#define SET_INTERFACE 11
#define SYNCH_FRAME 12
/* Descriptor Types */
#define DESC_DEVICE 1
#define DESC_CONFIGURATION 2
#define DESC_STRING 3
#define DESC_INTERFACE 4
#define DESC_ENDPOINT 5
#define DESC_DEVICE_QUALIFIER 6
#define DESC_OTHER_SPEED_CONFIGURATION 7
#define DESC_INTERFACE_POWER 8
#define DESC_CS_INTERFACE 0x24
#define USB_CLASS_CDC_DATA 0x0a
struct __packed usb_device_request {
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
};
extern const uint8_t device_descriptor[];
extern const uint8_t config_descriptor[];
#define NR_STRING_DESC 3
extern const char *string_descriptors[];
extern struct ep0 {
struct usb_device_request req;
uint8_t data[128];
int data_len;
struct {
const uint8_t *p;
int todo;
} tx;
} ep0;
#define ep0_data_out() (!(ep0.req.bmRequestType & 0x80))
#define ep0_data_in() (!ep0_data_out())
/* USB CDC ACM */
bool_t cdc_acm_handle_class_request(void);
bool_t cdc_acm_set_configuration(void);
/* USB Core */
extern uint8_t pending_addr;
bool_t handle_control_request(void);
/* USB Hardware */
void usb_configure_ep(uint8_t ep, uint8_t type, uint32_t size);
#define WARN printk
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

365
src/usb/hw_f1.c Normal file
View File

@@ -0,0 +1,365 @@
/*
* hw_f1.c
*
* USB handling for STM32F10x devices (except 105/107).
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
static uint16_t buf_end;
/* We track which endpoints have been marked CTR (Correct TRansfer).
* On receive side, checking EPR_STAT_RX == NAK races update of the
* Buffer Descriptor's COUNT_RX by the hardware. */
static bool_t _ep_rx_ready[8];
static bool_t _ep_tx_ready[8];
void usb_init(void)
{
/* Turn on clock. */
rcc->apb1enr |= RCC_APB1ENR_USBEN;
/* Exit power-down state. */
usb->cntr &= ~USB_CNTR_PDWN;
delay_us(10);
/* Exit reset state. */
usb->cntr &= ~USB_CNTR_FRES;
delay_us(10);
/* Clear IRQ state. */
usb->istr = 0;
/* Indicate we are connected by pulling up D+. */
gpio_configure_pin(gpioa, 0, GPO_pushpull(_2MHz, HIGH));
}
#if 0
static void dump_ep(uint8_t ep)
{
const static char *names[] = { "DISA", "STAL", "NAK ", "VALI" };
uint16_t epr;
ep &= 0x7f;
epr = usb->epr[ep];
printk("[EP%u: Rx:%c%c(%s)%04x:%02u Tx:%c%c(%s)%04x:%02u %c]",
ep,
(epr & USB_EPR_CTR_RX) ? 'C' : ' ',
(epr & USB_EPR_DTOG_RX) ? 'D' : ' ',
names[(epr>>12)&3],
usb_bufd[ep].addr_rx, usb_bufd[ep].count_rx & 0x3ff,
(epr & USB_EPR_CTR_TX) ? 'C' : ' ',
(epr & USB_EPR_DTOG_TX) ? 'D' : ' ',
names[(epr>>4)&3],
usb_bufd[ep].addr_tx, usb_bufd[ep].count_tx & 0x3ff,
(epr & USB_EPR_SETUP) ? 'S' : ' ');
}
#endif
int ep_rx_ready(uint8_t ep)
{
return _ep_rx_ready[ep] ? usb_bufd[ep].count_rx & 0x3ff : -1;
}
bool_t ep_tx_ready(uint8_t ep)
{
return _ep_tx_ready[ep];
}
void usb_read(uint8_t ep, void *buf, uint32_t len)
{
unsigned int i, base = (uint16_t)usb_bufd[ep].addr_rx >> 1;
uint16_t epr, *p = buf;
for (i = 0; i < len/2; i++)
*p++ = usb_buf[base + i];
if (len&1)
*(uint8_t *)p = usb_buf[base + i];
/* Clear CTR_RX and set status NAK->VALID. */
epr = usb->epr[ep];
epr &= 0x370f;
epr |= 0x0080;
epr ^= USB_EPR_STAT_RX(USB_STAT_VALID);
usb->epr[ep] = epr;
/* Await next CTR_RX notification. */
_ep_rx_ready[ep] = FALSE;
}
void usb_write(uint8_t ep, const void *buf, uint32_t len)
{
unsigned int i, base = (uint16_t)usb_bufd[ep].addr_tx >> 1;
uint16_t epr;
const uint16_t *p = buf;
for (i = 0; i < len/2; i++)
usb_buf[base + i] = *p++;
if (len&1)
usb_buf[base + i] = *(const uint8_t *)p;
usb_bufd[ep].count_tx = len;
/* Set status NAK->VALID. */
epr = usb->epr[ep];
epr &= 0x073f;
epr |= 0x8080;
epr ^= USB_EPR_STAT_TX(USB_STAT_VALID);
usb->epr[ep] = epr;
/* Await next CTR_TX notification. */
_ep_tx_ready[ep] = FALSE;
}
static void usb_continue_write_ep0(void)
{
uint32_t len;
if ((ep0.tx.todo < 0) || !ep_tx_ready(0))
return;
len = min_t(uint32_t, ep0.tx.todo, 64);
usb_write(0, ep0.tx.p, len);
if ((ep0.tx.todo -= len) == 0)
ep0.tx.todo = -1;
ep0.tx.p += len;
}
static void usb_start_write_ep0(const void *buf, uint32_t len)
{
ep0.tx.p = buf;
ep0.tx.todo = len;
usb_continue_write_ep0();
}
static void usb_stall(uint8_t ep)
{
uint16_t epr = usb->epr[ep];
epr &= 0x073f;
epr |= 0x8080;
epr ^= USB_EPR_STAT_TX(USB_STAT_STALL);
usb->epr[ep] = epr;
}
void usb_configure_ep(uint8_t ep, uint8_t type, uint32_t size)
{
uint16_t old_epr, new_epr;
bool_t in;
in = !!(ep & 0x80);
ep &= 0x7f;
old_epr = usb->epr[ep];
/* Sets: Type and Endpoint Address.
* Clears: CTR_RX and CTR_TX. */
new_epr = USB_EPR_EP_TYPE(type) | USB_EPR_EA(ep);
if (in || (ep == 0)) {
usb_bufd[ep].addr_tx = buf_end;
buf_end += size;
usb_bufd[ep].count_tx = 0;
/* TX: Clears data toggle and sets status to NAK. */
new_epr |= (old_epr & 0x0070) ^ USB_EPR_STAT_TX(USB_STAT_NAK);
/* IN Endpoint is immediately ready to transmit. */
_ep_tx_ready[ep] = TRUE;
}
if (!in) {
usb_bufd[ep].addr_rx = buf_end;
buf_end += size;
usb_bufd[ep].count_rx = 0x8400; /* 64 bytes */
/* RX: Clears data toggle and sets status to VALID. */
new_epr |= (old_epr & 0x7000) ^ USB_EPR_STAT_RX(USB_STAT_VALID);
/* OUT Endpoint must wait for a packet from the Host. */
_ep_rx_ready[ep] = FALSE;
}
usb->epr[ep] = new_epr;
}
static void handle_reset(void)
{
/* Reinitialise floppy subsystem. */
floppy_reset();
/* All Endpoints in invalid Tx/Rx state. */
memset(_ep_rx_ready, 0, sizeof(_ep_rx_ready));
memset(_ep_tx_ready, 0, sizeof(_ep_tx_ready));
/* Clear any in-progress Control Transfer. */
ep0.data_len = -1;
ep0.tx.todo = -1;
/* Prepare for Enumeration: Set up Endpoint 0 at Address 0. */
pending_addr = 0;
buf_end = 64;
usb_configure_ep(0, USB_EP_TYPE_CONTROL, 64);
usb->daddr = USB_DADDR_EF | USB_DADDR_ADD(0);
usb->istr &= ~USB_ISTR_RESET;
}
static void handle_rx_transfer(uint8_t ep)
{
uint16_t epr;
bool_t ready;
/* Clear CTR_RX. */
epr = usb->epr[ep];
epr &= 0x070f;
epr |= 0x0080;
usb->epr[ep] = epr;
_ep_rx_ready[ep] = TRUE;
/* We only handle Control Transfers here (endpoint 0). */
if (ep != 0)
return;
ready = FALSE;
epr = usb->epr[ep];
if (epr & USB_EPR_SETUP) {
/* Control Transfer: Setup Stage. */
ep0.data_len = 0;
ep0.tx.todo = -1;
usb_read(ep, &ep0.req, sizeof(ep0.req));
ready = ep0_data_in() || (ep0.req.wLength == 0);
} else if (ep0.data_len < 0) {
/* Unexpected Transaction */
usb_stall(0);
usb_read(ep, NULL, 0);
} else if (ep0_data_out()) {
/* OUT Control Transfer: Data from Host. */
uint32_t len = usb_bufd[ep].count_rx & 0x3ff;
int l = 0;
if (ep0.data_len < sizeof(ep0.data))
l = min_t(int, sizeof(ep0.data)-ep0.data_len, len);
usb_read(ep, &ep0.data[ep0.data_len], l);
ep0.data_len += len;
if (ep0.data_len >= ep0.req.wLength) {
ep0.data_len = ep0.req.wLength; /* clip */
ready = TRUE;
}
} else {
/* IN Control Transfer: Status from Host. */
usb_read(ep, NULL, 0);
ep0.data_len = -1; /* Complete */
}
/* Are we ready to handle the Control Request? */
if (!ready)
return;
/* Attempt to handle the Control Request: */
if (!handle_control_request()) {
/* Unhandled Control Transfer: STALL */
usb_stall(0);
ep0.data_len = -1; /* Complete */
} else if (ep0_data_in()) {
/* IN Control Transfer: Send Data to Host. */
usb_start_write_ep0(ep0.data, ep0.data_len);
} else {
/* OUT Control Transfer: Send Status to Host. */
usb_start_write_ep0(NULL, 0);
ep0.data_len = -1; /* Complete */
}
}
static void handle_tx_transfer(uint8_t ep)
{
uint16_t epr;
/* Clear CTR_TX. */
epr = usb->epr[ep];
epr &= 0x070f;
epr |= 0x8000;
usb->epr[ep] = epr;
_ep_tx_ready[ep] = TRUE;
/* We only handle Control Transfers here (endpoint 0). */
if (ep != 0)
return;
usb_continue_write_ep0();
if (pending_addr) {
/* We have just completed the Status stage of a SET_ADDRESS request.
* Now is the time to apply the address update. */
usb->daddr = USB_DADDR_EF | USB_DADDR_ADD(pending_addr);
pending_addr = 0;
}
}
void usb_process(void)
{
uint16_t istr = usb->istr;
usb->istr = ~istr & 0x7f00;
if (istr & USB_ISTR_CTR) {
uint8_t ep = USB_ISTR_GET_EP_ID(istr);
//dump_ep(ep);
if (istr & USB_ISTR_DIR)
handle_rx_transfer(ep);
else
handle_tx_transfer(ep);
//printk(" -> "); dump_ep(ep); printk("\n");
}
if (istr & USB_ISTR_PMAOVR) {
printk("[PMAOVR]\n");
}
if (istr & USB_ISTR_ERR) {
printk("[ERR]\n");
}
if (istr & USB_ISTR_WKUP) {
printk("[WKUP]\n");
}
if (istr & USB_ISTR_RESET) {
printk("[RESET]\n");
handle_reset();
}
/* We ignore all the below... */
if (istr & USB_ISTR_SUSP) {
// printk("[SUSP]\n");
}
if (istr & USB_ISTR_SOF) {
// printk("[SOF]\n");
}
if (istr & USB_ISTR_ESOF) {
// printk("[ESOF]\n");
}
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

245
src/util.c Normal file
View File

@@ -0,0 +1,245 @@
/*
* util.c
*
* General-purpose utility functions.
*
* Written & released by Keir Fraser <keir.xen@gmail.com>
*
* This is free and unencumbered software released into the public domain.
* See the file COPYING for more details, or visit <http://unlicense.org>.
*/
void filename_extension(const char *filename, char *extension, size_t size)
{
const char *p;
unsigned int i;
extension[0] = '\0';
if ((p = strrchr(filename, '.')) == NULL)
return;
for (i = 0; i < (size-1); i++)
if ((extension[i] = tolower(p[i+1])) == '\0')
break;
extension[i] = '\0';
}
void *memset(void *s, int c, size_t n)
{
char *p = s;
/* Large aligned memset? */
size_t n32 = n & ~31;
if (n32 && !((uint32_t)p & 3)) {
memset_fast(p, c, n32);
p += n32;
n &= 31;
}
/* Remainder/unaligned memset. */
while (n--)
*p++ = c;
return s;
}
void *memcpy(void *dest, const void *src, size_t n)
{
char *p = dest;
const char *q = src;
/* Large aligned copy? */
size_t n32 = n & ~31;
if (n32 && !(((uint32_t)p | (uint32_t)q) & 3)) {
memcpy_fast(p, q, n32);
p += n32;
q += n32;
n &= 31;
}
/* Remainder/unaligned copy. */
while (n--)
*p++ = *q++;
return dest;
}
asm (
".global memcpy_fast, memset_fast\n"
"memcpy_fast:\n"
" push {r4-r10}\n"
"1: ldmia r1!,{r3-r10}\n"
" stmia r0!,{r3-r10}\n"
" subs r2,r2,#32\n"
" bne 1b\n"
" pop {r4-r10}\n"
" bx lr\n"
"memset_fast:\n"
" push {r4-r10}\n"
" uxtb r5, r1\n"
" mov.w r4, #0x01010101\n"
" muls r4, r5\n"
" mov r3, r4\n"
" mov r5, r4\n"
" mov r6, r4\n"
" mov r7, r4\n"
" mov r8, r4\n"
" mov r9, r4\n"
" mov r10, r4\n"
"1: stmia r0!,{r3-r10}\n"
" subs r2,r2,#32\n"
" bne 1b\n"
" pop {r4-r10}\n"
" bx lr\n"
);
void *memmove(void *dest, const void *src, size_t n)
{
char *p;
const char *q;
if (dest < src)
return memcpy(dest, src, n);
p = dest; p += n;
q = src; q += n;
while (n--)
*--p = *--q;
return dest;
}
int memcmp(const void *s1, const void *s2, size_t n)
{
const char *_s1 = s1;
const char *_s2 = s2;
while (n--) {
int diff = *_s1++ - *_s2++;
if (diff)
return diff;
}
return 0;
}
size_t strlen(const char *s)
{
size_t len = 0;
while (*s++)
len++;
return len;
}
size_t strnlen(const char *s, size_t maxlen)
{
size_t len = 0;
while (maxlen-- && *s++)
len++;
return len;
}
int strcmp(const char *s1, const char *s2)
{
return strncmp(s1, s2, ~0);
}
int strncmp(const char *s1, const char *s2, size_t n)
{
while (n--) {
int diff = *s1 - *s2;
if (diff || !*s1)
return diff;
s1++; s2++;
}
return 0;
}
char *strrchr(const char *s, int c)
{
char *p = NULL;
int d;
while ((d = *s)) {
if (d == c) p = (char *)s;
s++;
}
return p;
}
char *strcpy(char *dest, const char *src)
{
char *p = dest;
const char *q = src;
while ((*p++ = *q++) != '\0')
continue;
return dest;
}
int tolower(int c)
{
if ((c >= 'A') && (c <= 'Z'))
c += 'a' - 'A';
return c;
}
int isspace(int c)
{
return (c == ' ') || (c == '\t') || (c == '\n') || (c == '\r')
|| (c == '\f') || (c == '\v');
}
long int strtol(const char *nptr, char **endptr, int base)
{
long int val = 0;
const char *p = nptr;
bool_t is_neg = FALSE;
int c;
/* Optional whitespace prefix. */
while (isspace(*p))
p++;
c = tolower(*p);
/* Optional sign prefix: +, -. */
if ((c == '+') || (c == '-')) {
is_neg = (c == '-');
c = tolower(*++p);
}
/* Optional base prefix: 0, 0x. */
if (c == '0') {
if (base == 0)
base = 8;
c = tolower(*++p);
if (c == 'x') {
if (base == 0)
base = 16;
if (base != 16)
goto out;
c = tolower(*++p);
}
}
/* Digits. */
for (;;) {
/* Convert c to a digit [0123456789abcdefghijklmnopqrstuvwxyz]. */
if ((c >= '0') && (c <= '9'))
c -= '0';
else if ((c >= 'a') && (c <= 'z'))
c -= 'a' - 10;
else
break;
if (c >= base)
break;
val = (val * base) + c;
c = tolower(*++p);
}
out:
if (endptr)
*endptr = (char *)p;
return is_neg ? -val : val;
}
/*
* Local variables:
* mode: C
* c-file-style: "Linux"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/

45
src/vectors.S Normal file
View File

@@ -0,0 +1,45 @@
.syntax unified
.section .vector_table
.global vector_table
vector_table:
/* Top of stack */
.word _thread_stacktop
/* Exceptions (1-15) */
#define E(x) .word x; .weak x; .thumb_set x, EXC_unused;
E(EXC_reset)
E(EXC_nmi)
E(EXC_hard_fault)
E(EXC_memory_management_fault)
E(EXC_bus_fault)
E(EXC_usage_fault)
E(EXC_7)
E(EXC_8)
E(EXC_9)
E(EXC_10)
E(EXC_sv_call)
E(EXC_12)
E(EXC_13)
E(EXC_pend_sv)
E(EXC_systick)
/* Interrupts/IRQs (0-67) */
#define I(n) E(IRQ_##n)
I( 0) I( 1) I( 2) I( 3) I( 4) I( 5) I( 6) I( 7) I( 8) I( 9)
I(10) I(11) I(12) I(13) I(14) I(15) I(16) I(17) I(18) I(19)
I(20) I(21) I(22) I(23) I(24) I(25) I(26) I(27) I(28) I(29)
I(30) I(31) I(32) I(33) I(34) I(35) I(36) I(37) I(38) I(39)
I(40) I(41) I(42) I(43) I(44) I(45) I(46) I(47) I(48) I(49)
I(50) I(51) I(52) I(53) I(54) I(55) I(56) I(57) I(58) I(59)
I(60) I(61) I(62) I(63) I(64) I(65) I(66) I(67)
.text
.thumb_func
.global EXC_unused
EXC_unused:
push {r4, r5, r6, r7, r8, r9, r10, r11, lr}
mov r0, sp
b EXC_unexpected