diff options
Diffstat (limited to 'src')
56 files changed, 5503 insertions, 116 deletions
diff --git a/src/Makefile b/src/Makefile index 9e31e3ea..e271ddf3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,7 +18,7 @@ SDCCDIRS=\ telemetrum-v1.2 telemetrum-v1.1 telemetrum-v1.0 \ teledongle-v0.2 teledongle-v0.1 \ telemini-v1.0 telenano-v0.1 \ - telebt-v0.0 telebt-v0.1 \ + telebt-v1.0 \ telemetrum-v0.1-sky telemetrum-v0.1-sirf \ telelaunch-v0.1 tidongle test \ teleterra-v0.2 teleshield-v0.1 \ @@ -29,7 +29,7 @@ AVRDIRS=\ telescience-v0.1 telescience-pwm telepyro-v0.1 micropeak ARMDIRS=\ - megametrum-v0.1 megadongle-v0.1 stm-bringup stm-demo telelco-v0.1 \ + telemega-v0.1 megadongle-v0.1 stm-bringup stm-demo telelco-v0.1 \ telescience-v0.2 ifneq ($(shell which sdcc),) diff --git a/src/cc1111/ao_adc.c b/src/cc1111/ao_adc.c index bfdc418a..ed76179b 100644 --- a/src/cc1111/ao_adc.c +++ b/src/cc1111/ao_adc.c @@ -153,6 +153,15 @@ ao_adc_isr(void) __interrupt 1 #define GOT_ADC #endif /* TELEFIRE_V_0_1 */ +#ifdef TELEBT_V_1_0 + a = (uint8_t __xdata *) (&ao_data_ring[ao_data_head].adc.batt); + a[0] = ADCL; + a[1] = ADCH; + if (0) + ; +#define GOT_ADC +#endif + #ifndef GOT_ADC #error No known ADC configuration set #endif diff --git a/src/core/ao.h b/src/core/ao.h index 7ff49dd5..548e8738 100644 --- a/src/core/ao.h +++ b/src/core/ao.h @@ -65,6 +65,7 @@ #define AO_PANIC_STACK 12 /* Stack overflow */ #define AO_PANIC_SPI 13 /* SPI communication failure */ #define AO_PANIC_CRASH 14 /* Processor crashed */ +#define AO_PANIC_BUFIO 15 /* Mis-using bufio API */ #define AO_PANIC_SELF_TEST_CC1120 0x40 | 1 /* Self test failure */ #define AO_PANIC_SELF_TEST_HMC5883 0x40 | 2 /* Self test failure */ #define AO_PANIC_SELF_TEST_MPU6000 0x40 | 3 /* Self test failure */ @@ -93,7 +94,7 @@ extern volatile __data AO_TICK_TYPE ao_tick_count; #define AO_SEC_TO_TICKS(s) ((s) * AO_HERTZ) /* Returns the current time in ticks */ -uint16_t +AO_TICK_TYPE ao_time(void); /* Suspend the current task until ticks time has passed */ @@ -517,17 +518,28 @@ extern __xdata uint8_t ao_radio_dma; #define AO_RADIO_STATUS_CRC_OK AO_FEC_DECODE_CRC_OK #endif +#ifndef HAS_RADIO_RECV +#define HAS_RADIO_RECV HAS_RADIO +#endif +#ifndef HAS_RADIO_XMIT +#define HAS_RADIO_XMIT HAS_RADIO +#endif + void ao_radio_general_isr(void) ao_arch_interrupt(16); +#if HAS_RADIO_XMIT void ao_radio_send(const __xdata void *d, uint8_t size) __reentrant; +#endif +#if HAS_RADIO_RECV uint8_t ao_radio_recv(__xdata void *d, uint8_t size) __reentrant; void ao_radio_recv_abort(void); +#endif void ao_radio_test(uint8_t on); @@ -535,7 +547,26 @@ ao_radio_test(uint8_t on); typedef int16_t (*ao_radio_fill_func)(uint8_t *buffer, int16_t len); void -ao_radio_send_lots(ao_radio_fill_func fill); +ao_radio_send_aprs(ao_radio_fill_func fill); + +/* + * ao_radio_pa + */ + +#if HAS_RADIO_AMP +void +ao_radio_pa_on(void); + +void +ao_radio_pa_off(void); + +void +ao_radio_pa_init(void); +#else +#define ao_radio_pa_on() +#define ao_radio_pa_off() +#define ao_radio_pa_init() +#endif /* * Compute the packet length as follows: @@ -687,7 +718,7 @@ extern __xdata uint8_t ao_force_freq; #endif #define AO_CONFIG_MAJOR 1 -#define AO_CONFIG_MINOR 13 +#define AO_CONFIG_MINOR 14 #define AO_AES_LEN 16 @@ -715,6 +746,12 @@ struct ao_config { struct ao_pyro pyro[AO_PYRO_NUM]; /* minor version 12 */ #endif uint16_t aprs_interval; /* minor version 13 */ +#if HAS_RADIO_POWER + uint8_t radio_power; /* minor version 14 */ +#endif +#if HAS_RADIO_AMP + uint8_t radio_amp; /* minor version 14 */ +#endif }; #define AO_IGNITE_MODE_DUAL 0 diff --git a/src/core/ao_config.c b/src/core/ao_config.c index 0aac16a6..73608a55 100644 --- a/src/core/ao_config.c +++ b/src/core/ao_config.c @@ -47,6 +47,8 @@ __xdata uint8_t ao_config_mutex; #define AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX ((uint32_t) 192 * (uint32_t) 1024) #endif #endif +#define AO_CONFIG_DEFAULT_RADIO_POWER 0x60 +#define AO_CONFIG_DEFAULT_RADIO_AMP 0 #if HAS_EEPROM static void @@ -141,6 +143,14 @@ _ao_config_get(void) #endif if (minor < 13) ao_config.aprs_interval = 0; +#if HAS_RADIO_POWER + if (minor < 14) + ao_config.radio_power = AO_CONFIG_DEFAULT_RADIO_POWER; + #endif +#if HAS_RADIO_AMP + if (minor < 14) + ao_config.radio_amp = AO_CONFIG_DEFAULT_RADIO_AMP; +#endif ao_config.minor = AO_CONFIG_MINOR; ao_config_dirty = 1; } @@ -210,6 +220,7 @@ ao_config_callsign_set(void) __reentrant } #if HAS_RADIO + void ao_config_frequency_show(void) __reentrant { @@ -227,7 +238,9 @@ ao_config_frequency_set(void) __reentrant ao_config.frequency = ao_cmd_lex_u32; ao_config_set_radio(); _ao_config_edit_finish(); +#if HAS_RADIO_RECV ao_radio_recv_abort(); +#endif } #endif @@ -521,6 +534,48 @@ ao_config_aprs_set(void) #endif /* HAS_APRS */ +#if HAS_RADIO_AMP + +void +ao_config_radio_amp_show(void) +{ + printf ("Radio amp setting: %d\n", ao_config.radio_amp); +} + +void +ao_config_radio_amp_set(void) +{ + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + _ao_config_edit_start(); + ao_config.radio_amp = ao_cmd_lex_i; + _ao_config_edit_finish(); +} + +#endif + +#if HAS_RADIO_POWER + +void +ao_config_radio_power_show(void) +{ + printf ("Radio power setting: %d\n", ao_config.radio_power); +} + +void +ao_config_radio_power_set(void) +{ + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + _ao_config_edit_start(); + ao_config.radio_power = ao_cmd_lex_i; + _ao_config_edit_finish(); +} + +#endif + struct ao_config_var { __code char *str; void (*set)(void) __reentrant; @@ -554,6 +609,14 @@ __code struct ao_config_var ao_config_vars[] = { ao_config_radio_enable_set, ao_config_radio_enable_show }, { "f <cal>\0Radio calib (cal = rf/(xtal/2^16))", ao_config_radio_cal_set, ao_config_radio_cal_show }, +#if HAS_RADIO_POWER + { "p <setting>\0Radio power setting (0-255)", + ao_config_radio_power_set, ao_config_radio_power_show }, +#endif +#if HAS_RADIO_AMP + { "d <setting>\0Radio amplifier setting (0-3)", + ao_config_radio_amp_set, ao_config_radio_amp_show }, +#endif #endif /* HAS_RADIO */ #if HAS_ACCEL { "a <+g> <-g>\0Accel calib (0 for auto)", diff --git a/src/core/ao_fec_rx.c b/src/core/ao_fec_rx.c index 072a9e90..c4f5559a 100644 --- a/src/core/ao_fec_rx.c +++ b/src/core/ao_fec_rx.c @@ -18,7 +18,7 @@ #include <ao_fec.h> #include <stdio.h> -#ifdef MEGAMETRUM +#ifdef TELEMEGA #include <ao.h> #endif diff --git a/src/core/ao_gps_report_mega.c b/src/core/ao_gps_report_mega.c index 47891cab..e3af4307 100644 --- a/src/core/ao_gps_report_mega.c +++ b/src/core/ao_gps_report_mega.c @@ -16,6 +16,7 @@ */ #include "ao.h" +#include "ao_log.h" void ao_gps_report_mega(void) diff --git a/src/core/ao_log.h b/src/core/ao_log.h index 036d6f2d..a68a40dd 100644 --- a/src/core/ao_log.h +++ b/src/core/ao_log.h @@ -43,7 +43,7 @@ extern __pdata enum ao_flight_state ao_log_state; #define AO_LOG_FORMAT_TINY 2 /* two byte state/baro records */ #define AO_LOG_FORMAT_TELEMETRY 3 /* 32 byte ao_telemetry records */ #define AO_LOG_FORMAT_TELESCIENCE 4 /* 32 byte typed telescience records */ -#define AO_LOG_FORMAT_MEGAMETRUM 5 /* 32 byte typed megametrum records */ +#define AO_LOG_FORMAT_TELEMEGA 5 /* 32 byte typed telemega records */ #define AO_LOG_FORMAT_NONE 127 /* No log at all */ extern __code uint8_t ao_log_format; @@ -268,4 +268,7 @@ ao_log_data(__xdata struct ao_log_record *log) __reentrant; uint8_t ao_log_mega(__xdata struct ao_log_mega *log) __reentrant; +void +ao_log_flush(void); + #endif /* _AO_LOG_H_ */ diff --git a/src/core/ao_log_mega.c b/src/core/ao_log_mega.c index e03687ad..abf953a6 100644 --- a/src/core/ao_log_mega.c +++ b/src/core/ao_log_mega.c @@ -23,7 +23,7 @@ static __xdata uint8_t ao_log_mutex; static __xdata struct ao_log_mega log; -__code uint8_t ao_log_format = AO_LOG_FORMAT_MEGAMETRUM; +__code uint8_t ao_log_format = AO_LOG_FORMAT_TELEMEGA; static uint8_t ao_log_csum(__xdata uint8_t *b) __reentrant @@ -171,6 +171,8 @@ ao_log(void) } #endif + ao_log_flush(); + /* Wait for a while */ ao_delay(AO_MS_TO_TICKS(100)); diff --git a/src/core/ao_telemetry.c b/src/core/ao_telemetry.c index 8d440e15..03aa48d8 100644 --- a/src/core/ao_telemetry.c +++ b/src/core/ao_telemetry.c @@ -16,6 +16,7 @@ */ #include "ao.h" +#include "ao_log.h" #include "ao_product.h" static __pdata uint16_t ao_telemetry_interval; @@ -28,7 +29,7 @@ static __pdata uint16_t ao_aprs_time; #include <ao_aprs.h> #endif -#if defined(MEGAMETRUM) +#if defined(TELEMEGA) #define AO_SEND_MEGA 1 #endif @@ -306,12 +307,14 @@ ao_telemetry(void) #ifdef AO_SEND_ALL_BARO ao_send_baro(); #endif +#if HAS_FLIGHT #ifdef AO_SEND_MEGA ao_send_mega_sensor(); ao_send_mega_data(); #else ao_send_sensor(); #endif +#endif #if HAS_COMPANION if (ao_companion_running) diff --git a/src/drivers/ao_aprs.c b/src/drivers/ao_aprs.c index 03bcab05..6ab61e6a 100644 --- a/src/drivers/ao_aprs.c +++ b/src/drivers/ao_aprs.c @@ -593,7 +593,7 @@ void ao_aprs_send(void) tncIndex = 0; tncMode = TNC_TX_SYNC; - ao_radio_send_lots(tncFill); + ao_radio_send_aprs(tncFill); } /** @} */ diff --git a/src/drivers/ao_btm.c b/src/drivers/ao_btm.c index de1f31a3..3b6028a0 100644 --- a/src/drivers/ao_btm.c +++ b/src/drivers/ao_btm.c @@ -302,7 +302,7 @@ ao_btm(void) while (!ao_btm_connected) ao_sleep(&ao_btm_connected); while (ao_btm_connected) { - ao_led_for(AO_LED_GREEN, AO_MS_TO_TICKS(20)); + ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(20)); ao_delay(AO_SEC_TO_TICKS(3)); } } diff --git a/src/drivers/ao_bufio.c b/src/drivers/ao_bufio.c new file mode 100644 index 00000000..c0fe604a --- /dev/null +++ b/src/drivers/ao_bufio.c @@ -0,0 +1,321 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef AO_FAT_TEST +#include "ao.h" +#endif + +/* Include bufio commands */ +#ifndef AO_FAT_TEST +#define BUFIO_COMMANDS 0 +#endif + +#include "ao_sdcard.h" +#include "ao_bufio.h" + +#define AO_NUM_BUF 16 +#define AO_BUFSIZ 512 + +struct ao_bufio { + uint32_t block; + int16_t seqno; + uint8_t busy; + uint8_t dirty; +}; + +static struct ao_bufio ao_bufio[AO_NUM_BUF]; +static uint8_t ao_buffer[AO_NUM_BUF][AO_BUFSIZ]; +static int16_t ao_seqno; +static uint8_t ao_bufio_mutex; + +#if 0 +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +static inline void +ao_bufio_lock(void) +{ + ao_mutex_get(&ao_bufio_mutex); +} + +static inline void +ao_bufio_unlock(void) +{ + ao_mutex_put(&ao_bufio_mutex); +} + +static inline int16_t +ao_seqno_age(int16_t s) +{ + return ao_seqno - s; +} + +static inline int16_t +ao_seqno_next(void) +{ + return ++ao_seqno; +} + +static inline int +ao_seqno_older(int16_t a, int16_t b) +{ + return ao_seqno_age(a) > ao_seqno_age(b); +} + +static inline void +ao_validate_bufno(int b) +{ + if (b < 0 || AO_NUM_BUF <= b) + ao_panic(AO_PANIC_BUFIO); +} + +static inline int +ao_buf_to_num(uint8_t *buf) +{ + int b = (buf - &ao_buffer[0][0]) / AO_BUFSIZ; + + ao_validate_bufno(b); + return b; +} + +static inline int +ao_bufio_to_num(struct ao_bufio *bufio) +{ + int b = (bufio - ao_bufio); + + ao_validate_bufno(b); + return b; +} + +static inline struct ao_bufio * +ao_buf_to_bufio(uint8_t *buf) +{ + int b = ao_buf_to_num(buf); + struct ao_bufio *bufio; + + bufio = &ao_bufio[b]; + DBG ("buf %08x is %d bufio %08x\n", buf, b, bufio); + return bufio; +} + +static inline uint8_t * +ao_bufio_to_buf(struct ao_bufio *bufio) +{ + int b = ao_bufio_to_num(bufio); + uint8_t *buf; + + buf = &ao_buffer[b][0]; + DBG ("bufio %08x is %d buf %08x\n", bufio, b, buf); + return buf; +} + +/* + * Write a buffer to storage if it is dirty + */ +static void +ao_bufio_write(struct ao_bufio *bufio) +{ + if (bufio->dirty) { + ao_sdcard_write_block(bufio->block, ao_bufio_to_buf(bufio)); + bufio->dirty = 0; + } +} + +/* + * Read a buffer from storage + */ +static uint8_t +ao_bufio_read(struct ao_bufio *bufio) +{ + uint8_t *buf = ao_bufio_to_buf(bufio); + + return ao_sdcard_read_block(bufio->block, buf); +} + +/* + * Find a buffer containing the specified block + */ +static struct ao_bufio * +ao_bufio_find_block(uint32_t block) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + struct ao_bufio *bufio = &ao_bufio[b]; + if (bufio->block == block) { + DBG ("Found existing buffer %d (seqno %d)\n", + ao_bufio_to_num(bufio), bufio->seqno); + return bufio; + } + } + return NULL; +} + +/* + * Find the least recently used idle buffer + */ +static struct ao_bufio * +ao_bufio_find_idle(void) +{ + int b; + struct ao_bufio *oldest = NULL; + + for (b = 0; b < AO_NUM_BUF; b++) { + struct ao_bufio *bufio = &ao_bufio[b]; + if (!bufio->busy) + if (!oldest || ao_seqno_older(bufio->seqno, oldest->seqno)) + oldest = bufio; + } + if (oldest) + DBG ("Using idle buffer %d (seqno %d)\n", + ao_bufio_to_num(oldest), oldest->seqno); + return oldest; +} + +/* + * Return a pointer to a buffer containing + * the contents of the specified block + */ +uint8_t * +ao_bufio_get(uint32_t block) +{ + struct ao_bufio *bufio; + uint8_t *buf = NULL; + + ao_bufio_lock(); + bufio = ao_bufio_find_block(block); + if (!bufio) { + bufio = ao_bufio_find_idle(); + if (bufio) { + ao_bufio_write(bufio); + bufio->block = block; + DBG ("read buffer\n"); + if (!ao_bufio_read(bufio)) { + bufio->block = 0xffffffff; + bufio = NULL; + } + } else + ao_panic(AO_PANIC_BUFIO); + } + if (bufio) { + bufio->busy++; + if (!bufio->busy) + ao_panic(AO_PANIC_BUFIO); + buf = ao_bufio_to_buf(bufio); + } + ao_bufio_unlock(); + return buf; +} + +/* + * Release a buffer, marking it dirty + * if it has been written to + */ +void +ao_bufio_put(uint8_t *buf, uint8_t write) +{ + struct ao_bufio *bufio; + + ao_bufio_lock(); + bufio = ao_buf_to_bufio(buf); + + if (!bufio->busy) + ao_panic(AO_PANIC_BUFIO); + + DBG ("idle buffer %d write %d\n", ao_bufio_to_num(bufio), write); + bufio->dirty |= write; + if (!--bufio->busy) { + bufio->seqno = ao_seqno_next(); + DBG ("not busy, seqno %d\n", bufio->seqno); + } + ao_bufio_unlock(); +} + +/* + * Flush a single buffer immediately. Useful + * if write order is important + */ +void +ao_bufio_flush_one(uint8_t *buf) +{ + ao_bufio_lock(); + ao_bufio_write(ao_buf_to_bufio(buf)); + ao_bufio_unlock(); +} + +/* + * Flush all buffers to storage + */ +void +ao_bufio_flush(void) +{ + int b; + + ao_bufio_lock(); + for (b = 0; b < AO_NUM_BUF; b++) + ao_bufio_write(&ao_bufio[b]); + ao_bufio_unlock(); +} + +#if BUFIO_COMMANDS +static void +ao_bufio_test_read(void) +{ + uint8_t *buf; + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + if ((buf = ao_bufio_get(ao_cmd_lex_u32))) { + int i; + for (i = 0; i < 512; i++) { + printf (" %02x", buf[i]); + if ((i & 0xf) == 0xf) + printf("\n"); + } + ao_bufio_put(buf, 0); + } +} + +static const struct ao_cmds ao_bufio_cmds[] = { + { ao_bufio_test_read, "q\0Test bufio read" }, + { 0, NULL }, +}; +#endif + +void +ao_bufio_setup(void) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + ao_bufio[b].dirty = 0; + ao_bufio[b].busy = 0; + ao_bufio[b].block = 0xffffffff; + } +} + +void +ao_bufio_init(void) +{ + ao_bufio_setup(); + ao_sdcard_init(); +#if BUFIO_COMMANDS + ao_cmd_register(&ao_bufio_cmds[0]); +#endif +} diff --git a/src/drivers/ao_bufio.h b/src/drivers/ao_bufio.h new file mode 100644 index 00000000..6629f143 --- /dev/null +++ b/src/drivers/ao_bufio.h @@ -0,0 +1,39 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_BUFIO_H_ +#define _AO_BUFIO_H_ + +uint8_t * +ao_bufio_get(uint32_t block); + +void +ao_bufio_put(uint8_t *buf, uint8_t write); + +void +ao_bufio_flush_one(uint8_t *buf); + +void +ao_bufio_flush(void); + +void +ao_bufio_setup(void); + +void +ao_bufio_init(void); + +#endif /* _AO_BUFIO_H_ */ diff --git a/src/drivers/ao_cc1120.c b/src/drivers/ao_cc1120.c index 53bb5a62..a26eccbc 100644 --- a/src/drivers/ao_cc1120.c +++ b/src/drivers/ao_cc1120.c @@ -747,7 +747,7 @@ ao_radio_send(const void *d, uint8_t size) #define AO_RADIO_LOTS 64 void -ao_radio_send_lots(ao_radio_fill_func fill) +ao_radio_send_aprs(ao_radio_fill_func fill) { uint8_t buf[AO_RADIO_LOTS], *b; int cnt; diff --git a/src/drivers/ao_cc115l.c b/src/drivers/ao_cc115l.c new file mode 100644 index 00000000..30c56442 --- /dev/null +++ b/src/drivers/ao_cc115l.c @@ -0,0 +1,958 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <ao.h> +#include <ao_cc115l.h> +#include <ao_exti.h> +#include <ao_telemetry.h> +#include <ao_fec.h> + +#define AO_RADIO_MAX_SEND sizeof (struct ao_telemetry_generic) + +uint8_t ao_radio_mutex; + +static uint8_t ao_radio_fifo; /* fifo drained interrupt received */ +static uint8_t ao_radio_done; /* tx done interrupt received */ +static uint8_t ao_radio_wake; /* sleep address for radio interrupts */ +static uint8_t ao_radio_abort; /* radio operation should abort */ +static uint8_t ao_radio_mcu_wake; /* MARC status change */ +static uint8_t ao_radio_marcstate; /* Last read MARC state value */ + +/* Debugging commands */ +#define CC115L_DEBUG 0 + +/* Runtime tracing */ +#define CC115L_TRACE 0 + +#define FOSC 26000000 + +#define ao_radio_select() ao_spi_get_mask(AO_CC115L_SPI_CS_PORT,(1 << AO_CC115L_SPI_CS_PIN),AO_CC115L_SPI_BUS,AO_SPI_SPEED_1MHz) +#define ao_radio_deselect() ao_spi_put_mask(AO_CC115L_SPI_CS_PORT,(1 << AO_CC115L_SPI_CS_PIN),AO_CC115L_SPI_BUS) +#define ao_radio_spi_send(d,l) ao_spi_send((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_spi_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_spi_recv(d,l) ao_spi_recv((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_duplex(o,i,l) ao_spi_duplex((o), (i), (l), AO_CC115L_SPI_BUS) + +struct ao_cc115l_reg { + uint16_t addr; + char *name; +}; + +#if CC115L_TRACE + +const static struct ao_cc115l_reg ao_cc115l_reg[]; +const static char *cc115l_state_name[]; + +enum ao_cc115l_trace_type { + trace_strobe, + trace_read, + trace_write, + trace_dma, + trace_line, +}; + +struct ao_cc115l_trace { + enum ao_cc115l_trace_type type; + int16_t addr; + int16_t value; + const char *comment; +}; + +#define NUM_TRACE 256 + +static struct ao_cc115l_trace trace[NUM_TRACE]; +static int trace_i; +static int trace_disable; + +static void trace_add(enum ao_cc115l_trace_type type, int16_t addr, int16_t value, const char *comment) +{ + if (trace_disable) + return; + switch (type) { + case trace_read: + case trace_write: + comment = ao_cc115l_reg[addr].name; + break; + case trace_strobe: + comment = cc115l_state_name[(value >> 4) & 0x7]; + break; + } + trace[trace_i].type = type; + trace[trace_i].addr = addr; + trace[trace_i].value = value; + trace[trace_i].comment = comment; + if (++trace_i == NUM_TRACE) + trace_i = 0; +} +#else +#define trace_add(t,a,v,c) +#endif + +static uint8_t +ao_radio_reg_read(uint8_t addr) +{ + uint8_t data[1]; + uint8_t d; + + data[0] = ((1 << CC115L_READ) | + (0 << CC115L_BURST) | + addr); + ao_radio_select(); + ao_radio_spi_send(data, 1); + ao_radio_spi_recv(data, 1); + ao_radio_deselect(); + trace_add(trace_read, addr, data[0], NULL); + return data[0]; +} + +static void +ao_radio_reg_write(uint8_t addr, uint8_t value) +{ + uint8_t data[2]; + uint8_t d; + + trace_add(trace_write, addr, value, NULL); + data[0] = ((0 << CC115L_READ) | + (0 << CC115L_BURST) | + addr); + data[1] = value; + ao_radio_select(); + ao_radio_spi_send(data, 2); + ao_radio_deselect(); +} + +static void +ao_radio_burst_read_start (uint16_t addr) +{ + uint8_t data[1]; + uint8_t d; + + data[0] = ((1 << CC115L_READ) | + (1 << CC115L_BURST) | + addr); + ao_radio_select(); + ao_radio_spi_send(data, 1); +} + +static void +ao_radio_burst_read_stop (void) +{ + ao_radio_deselect(); +} + + +static uint8_t +ao_radio_strobe(uint8_t addr) +{ + uint8_t in; + + ao_radio_select(); + ao_radio_duplex(&addr, &in, 1); + ao_radio_deselect(); + trace_add(trace_strobe, addr, in, NULL); + return in; +} + +static uint8_t +ao_radio_fifo_write_start(void) +{ + uint8_t addr = ((0 << CC115L_READ) | + (1 << CC115L_BURST) | + CC115L_FIFO); + uint8_t status; + + ao_radio_select(); + ao_radio_duplex(&addr, &status, 1); + return status; +} + +static inline uint8_t ao_radio_fifo_write_stop(uint8_t status) { + ao_radio_deselect(); + return status; +} + +static uint8_t +ao_radio_fifo_write(uint8_t *data, uint8_t len) +{ + uint8_t status = ao_radio_fifo_write_start(); + trace_add(trace_dma, CC115L_FIFO, len, NULL); + ao_radio_spi_send(data, len); + return ao_radio_fifo_write_stop(status); +} + +static uint8_t +ao_radio_tx_fifo_space(void) +{ + return CC115L_FIFO_SIZE - (ao_radio_reg_read(CC115L_TXBYTES) & CC115L_TXBYTES_NUM_TX_BYTES_MASK); +} + +static uint8_t +ao_radio_status(void) +{ + return ao_radio_strobe (CC115L_SNOP); +} + +#define ao_radio_rdf_value 0x55 + +static uint8_t +ao_radio_get_marcstate(void) +{ + return ao_radio_reg_read(CC115L_MARCSTATE) & CC115L_MARCSTATE_MASK; +} + +static void +ao_radio_done_isr(void) +{ + ao_exti_disable(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN); + trace_add(trace_line, __LINE__, 0, "done_isr"); + ao_radio_done = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_fifo_isr(void) +{ + ao_exti_disable(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN); + trace_add(trace_line, __LINE__, 0, "fifo_isr"); + ao_radio_fifo = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_start_tx(void) +{ +} + +static void +ao_radio_idle(void) +{ + ao_radio_pa_off(); + for (;;) { + uint8_t state = ao_radio_strobe(CC115L_SIDLE); + if ((state >> CC115L_STATUS_STATE) == CC115L_STATUS_STATE_IDLE) + break; + } + /* Flush any pending TX bytes */ + ao_radio_strobe(CC115L_SFTX); +} + +/* + * Packet deviation is 20.5kHz + * + * fdev = fosc >> 17 * (8 + dev_m) << dev_e + * + * 26e6 / (2 ** 17) * (8 + 5) * (2 ** 3) = 20630Hz + */ + +#define PACKET_DEV_E 3 +#define PACKET_DEV_M 5 + +/* + * For our packet data, set the symbol rate to 38400 Baud + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 131) * (2 ** 10) / (2**28) * 26e6 = 38383 + * + * DATARATE_M = 131 + * DATARATE_E = 10 + */ +#define PACKET_DRATE_E 10 +#define PACKET_DRATE_M 131 + +static const uint16_t packet_setup[] = { + CC115L_DEVIATN, ((PACKET_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (PACKET_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (PACKET_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (PACKET_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_16BITS << CC115L_MDMCFG2_SYNC_MODE)), +}; + + +/* + * RDF deviation is 5kHz + * + * fdev = fosc >> 17 * (8 + dev_m) << dev_e + * + * 26e6 / (2 ** 17) * (8 + 4) * (2 ** 1) = 4761Hz + */ + +#define RDF_DEV_E 1 +#define RDF_DEV_M 4 + +/* + * For our RDF beacon, set the symbol rate to 2kBaud (for a 1kHz tone) + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 67) * (2 ** 6) / (2**28) * 26e6 = 2002 + * + * DATARATE_M = 67 + * DATARATE_E = 6 + */ +#define RDF_DRATE_E 6 +#define RDF_DRATE_M 67 + +static const uint16_t rdf_setup[] = { + CC115L_DEVIATN, ((RDF_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (RDF_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (RDF_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (RDF_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_NONE << CC115L_MDMCFG2_SYNC_MODE)), +}; + +/* + * APRS deviation is the same as RDF + */ + +#define APRS_DEV_E RDF_DEV_E +#define APRS_DEV_M RDF_DEV_M + +/* + * For our APRS beacon, set the symbol rate to 9.6kBaud (8x oversampling for 1200 baud data rate) + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 131) * (2 ** 8) / (2**28) * 26e6 = 9596 + * + * DATARATE_M = 131 + * DATARATE_E = 8 + * + */ +#define APRS_DRATE_E 8 +#define APRS_DRATE_M 131 + +static const uint16_t aprs_setup[] = { + CC115L_DEVIATN, ((APRS_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (APRS_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (APRS_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (APRS_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_NONE << CC115L_MDMCFG2_SYNC_MODE)), +}; + +#define AO_PKTCTRL0_INFINITE ((CC115L_PKTCTRL0_PKT_FORMAT_NORMAL << CC115L_PKTCTRL0_PKT_FORMAT) | \ + (0 << CC115L_PKTCTRL0_PKT_CRC_EN) | \ + (CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_INFINITE << CC115L_PKTCTRL0_PKT_LENGTH_CONFIG)) +#define AO_PKTCTRL0_FIXED ((CC115L_PKTCTRL0_PKT_FORMAT_NORMAL << CC115L_PKTCTRL0_PKT_FORMAT) | \ + (0 << CC115L_PKTCTRL0_PKT_CRC_EN) | \ + (CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_FIXED << CC115L_PKTCTRL0_PKT_LENGTH_CONFIG)) + +static uint16_t ao_radio_mode; + + +/* + * These set the data rate and modulation parameters + */ +#define AO_RADIO_MODE_BITS_PACKET_TX 1 +#define AO_RADIO_MODE_BITS_RDF 2 +#define AO_RADIO_MODE_BITS_APRS 4 + +/* + * Flips between infinite packet mode and fixed packet mode; + * we use infinite mode until the sender gives us the + * last chunk of data + */ +#define AO_RADIO_MODE_BITS_INFINITE 40 +#define AO_RADIO_MODE_BITS_FIXED 80 + +#define AO_RADIO_MODE_NONE 0 + +#define AO_RADIO_MODE_RDF AO_RADIO_MODE_BITS_RDF +#define AO_RADIO_MODE_PACKET_TX AO_RADIO_MODE_BITS_PACKET_TX +#define AO_RADIO_MODE_APRS AO_RADIO_MODE_BITS_APRS + +static void +ao_radio_set_mode(uint16_t new_mode) +{ + uint16_t changes; + int i; + + if (new_mode == ao_radio_mode) + return; + + changes = new_mode & (~ao_radio_mode); + if (changes & AO_RADIO_MODE_BITS_PACKET_TX) + for (i = 0; i < sizeof (packet_setup) / sizeof (packet_setup[0]); i += 2) + ao_radio_reg_write(packet_setup[i], packet_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_RDF) + for (i = 0; i < sizeof (rdf_setup) / sizeof (rdf_setup[0]); i += 2) + ao_radio_reg_write(rdf_setup[i], rdf_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_APRS) + for (i = 0; i < sizeof (aprs_setup) / sizeof (aprs_setup[0]); i += 2) + ao_radio_reg_write(aprs_setup[i], aprs_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_INFINITE) + ao_radio_reg_write(CC115L_PKTCTRL0, AO_PKTCTRL0_INFINITE); + + if (changes & AO_RADIO_MODE_BITS_FIXED) + ao_radio_reg_write(CC115L_PKTCTRL0, AO_PKTCTRL0_FIXED); + + ao_radio_mode = new_mode; +} + +/*************************************************************** + * SmartRF Studio(tm) Export + * + * Radio register settings specifed with address, value + * + * RF device: CC115L + * + ***************************************************************/ + +static const uint16_t radio_setup[] = { + + /* High when FIFO is above threshold, low when fifo is below threshold */ + AO_CC115L_FIFO_INT_GPIO_IOCFG, CC115L_IOCFG_GPIO_CFG_TXFIFO_THR, + + /* High when transmitter is running, low when off */ + AO_CC115L_DONE_INT_GPIO_IOCFG, CC115L_IOCFG_GPIO_CFG_PA_PD | (1 << CC115L_IOCFG_GPIO_INV), + + CC115L_FIFOTHR, 0x47, /* TX FIFO Thresholds */ + CC115L_MDMCFG1, (0x00 | + (CC115L_MDMCFG1_NUM_PREAMBLE_4 << CC115L_MDMCFG1_NUM_PREAMBLE) | + (1 << CC115L_MDMCFG1_CHANSPC_E)), + CC115L_MDMCFG0, 248, /* Channel spacing M value (100kHz channels) */ + CC115L_MCSM0, 0x38, /* Main Radio Control State Machine Configuration */ + CC115L_RESERVED_0X20, 0xfb, /* Use setting from SmartRF Studio */ + CC115L_FSCAL3, 0xe9, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL2, 0x2a, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL1, 0x00, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL0, 0x1f, /* Frequency Synthesizer Calibration */ + CC115L_TEST2, 0x81, /* Various Test Settings */ + CC115L_TEST1, 0x35, /* Various Test Settings */ + CC115L_TEST0, 0x09, /* Various Test Settings */ +}; + +static uint8_t ao_radio_configured = 0; + +static void +ao_radio_setup(void) +{ + int i; + + ao_radio_strobe(CC115L_SRES); + ao_delay(AO_MS_TO_TICKS(10)); + + for (i = 0; i < sizeof (radio_setup) / sizeof (radio_setup[0]); i += 2) + ao_radio_reg_write(radio_setup[i], radio_setup[i+1]); + + ao_radio_mode = 0; + + ao_config_get(); + + ao_radio_configured = 1; +} + +static void +ao_radio_set_len(uint8_t len) +{ + static uint8_t last_len; + + if (len != last_len) { + ao_radio_reg_write(CC115L_PKTLEN, len); + last_len = len; + } +} + +static void +ao_radio_get(void) +{ + static uint32_t last_radio_setting; + static uint8_t last_power_setting; + + ao_mutex_get(&ao_radio_mutex); + if (!ao_radio_configured) + ao_radio_setup(); + if (ao_config.radio_setting != last_radio_setting) { + ao_radio_reg_write(CC115L_FREQ2, ao_config.radio_setting >> 16); + ao_radio_reg_write(CC115L_FREQ1, ao_config.radio_setting >> 8); + ao_radio_reg_write(CC115L_FREQ0, ao_config.radio_setting); + last_radio_setting = ao_config.radio_setting; + } + if (ao_config.radio_power != last_power_setting) { + ao_radio_reg_write(CC115L_PA, ao_config.radio_power); + last_power_setting = ao_config.radio_power; + } +} + +static void +_ao_radio_send_lots(ao_radio_fill_func fill, uint8_t mode); + +#define ao_radio_put() ao_mutex_put(&ao_radio_mutex) + +struct ao_radio_tone { + uint8_t value; + uint8_t len; +}; + +struct ao_radio_tone *ao_radio_tone; +uint8_t ao_radio_tone_count; +uint8_t ao_radio_tone_current; +uint8_t ao_radio_tone_offset; + +int16_t +ao_radio_tone_fill(uint8_t *buf, int16_t len) +{ + int16_t ret = 0; + + while (len) { + int16_t this_time; + struct ao_radio_tone *t; + + /* Figure out how many to send of the current value */ + t = &ao_radio_tone[ao_radio_tone_current]; + this_time = t->len - ao_radio_tone_offset; + if (this_time > len) + this_time = len; + + /* queue the data */ + memset(buf, t->value, this_time); + + /* mark as sent */ + len -= this_time; + ao_radio_tone_offset += this_time; + ret += this_time; + + if (ao_radio_tone_offset >= t->len) { + ao_radio_tone_offset = 0; + ao_radio_tone_current++; + if (ao_radio_tone_current >= ao_radio_tone_count) { + trace_add(trace_line, __LINE__, ret, "done with tone"); + return -ret; + } + } + } + trace_add(trace_line, __LINE__, ret, "got some tone"); + return ret; +} + +static void +ao_radio_tone_run(struct ao_radio_tone *tones, int ntones) +{ + ao_radio_get(); + ao_radio_tone = tones; + ao_radio_tone_current = 0; + ao_radio_tone_offset = 0; + _ao_radio_send_lots(ao_radio_tone_fill, AO_RADIO_MODE_RDF); + ao_radio_put(); +} + +void +ao_radio_rdf(void) +{ + struct ao_radio_tone tone; + + tone.value = ao_radio_rdf_value; + tone.len = AO_RADIO_RDF_LEN; + ao_radio_tone_run(&tone, 1); +} + +void +ao_radio_continuity(uint8_t c) +{ + struct ao_radio_tone tones[7]; + uint8_t count = 0; + uint8_t i; + + for (i = 0; i < 3; i++) { + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_PAUSE_LEN; + count++; + if (i < c) + tones[count].value = ao_radio_rdf_value; + else + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_TONE_LEN; + count++; + } + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_PAUSE_LEN; + count++; + ao_radio_tone_run(tones, count); +} + +void +ao_radio_rdf_abort(void) +{ + ao_radio_abort = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_test_cmd(void) +{ + uint8_t mode = 2; + static uint8_t radio_on; + ao_cmd_white(); + if (ao_cmd_lex_c != '\n') { + ao_cmd_decimal(); + mode = (uint8_t) ao_cmd_lex_u32; + } + mode++; + if ((mode & 2) && !radio_on) { +#if HAS_MONITOR + ao_monitor_disable(); +#endif +#if PACKET_HAS_SLAVE + ao_packet_slave_stop(); +#endif + ao_radio_get(); + ao_radio_set_len(0xff); + ao_radio_set_mode(AO_RADIO_MODE_RDF|AO_RADIO_MODE_BITS_FIXED); + ao_radio_strobe(CC115L_SFTX); + ao_radio_pa_on(); + ao_radio_strobe(CC115L_STX); + radio_on = 1; + } + if (mode == 3) { + printf ("Hit a character to stop..."); flush(); + getchar(); + putchar('\n'); + } + if ((mode & 1) && radio_on) { + ao_radio_idle(); + ao_radio_put(); + radio_on = 0; +#if HAS_MONITOR + ao_monitor_enable(); +#endif + } +} + +static inline int16_t +ao_radio_gpio_bits(void) +{ + return AO_CC115L_DONE_INT_PORT->idr & ((1 << AO_CC115L_FIFO_INT_PIN) | + (1 << AO_CC115L_DONE_INT_PIN)); +} + +static void +ao_radio_wait_fifo(void) +{ + ao_arch_block_interrupts(); + while (!ao_radio_fifo && !ao_radio_done && !ao_radio_abort) { + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wait_fifo"); + ao_sleep(&ao_radio_wake); + } + ao_arch_release_interrupts(); + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wake bits"); + trace_add(trace_line, __LINE__, ao_radio_fifo, "wake fifo"); + trace_add(trace_line, __LINE__, ao_radio_done, "wake done"); + trace_add(trace_line, __LINE__, ao_radio_abort, "wake abort"); +} + +static void +ao_radio_wait_done(void) +{ + ao_arch_block_interrupts(); + while (!ao_radio_done && !ao_radio_abort) { + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wait_done"); + ao_sleep(&ao_radio_wake); + } + ao_arch_release_interrupts(); + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wake bits"); + trace_add(trace_line, __LINE__, ao_radio_fifo, "wake fifo"); + trace_add(trace_line, __LINE__, ao_radio_done, "wake done"); + trace_add(trace_line, __LINE__, ao_radio_abort, "wake abort"); +} + +static uint8_t tx_data[(AO_RADIO_MAX_SEND + 4) * 2]; + +static uint8_t *ao_radio_send_buf; +static int16_t ao_radio_send_len; + +static int16_t +ao_radio_send_fill(uint8_t *buf, int16_t len) +{ + int16_t this_time; + + this_time = ao_radio_send_len; + if (this_time > len) + this_time = len; + memcpy(buf, ao_radio_send_buf, this_time); + ao_radio_send_buf += this_time; + ao_radio_send_len -= this_time; + if (ao_radio_send_len == 0) + return -this_time; + return this_time; +} + +void +ao_radio_send(const void *d, uint8_t size) +{ + int i; + + ao_radio_get(); + ao_radio_send_len = ao_fec_encode(d, size, tx_data); + ao_radio_send_buf = tx_data; + _ao_radio_send_lots(ao_radio_send_fill, AO_RADIO_MODE_PACKET_TX); + ao_radio_put(); +} + +#define AO_RADIO_LOTS 64 + +static void +_ao_radio_send_lots(ao_radio_fill_func fill, uint8_t mode) +{ + uint8_t buf[AO_RADIO_LOTS], *b; + int cnt; + int total = 0; + uint8_t done = 0; + uint8_t started = 0; + uint8_t fifo_space; + + fifo_space = CC115L_FIFO_SIZE; + ao_radio_done = 0; + ao_radio_fifo = 0; + while (!done) { + cnt = (*fill)(buf, sizeof(buf)); + trace_add(trace_line, __LINE__, cnt, "send data count"); + if (cnt < 0) { + done = 1; + cnt = -cnt; + } + total += cnt; + + /* At the last buffer, set the total length */ + if (done) { + ao_radio_set_len(total & 0xff); + ao_radio_set_mode(mode | AO_RADIO_MODE_BITS_FIXED); + } else { + ao_radio_set_len(0xff); + ao_radio_set_mode(mode | AO_RADIO_MODE_BITS_INFINITE); + } + + b = buf; + while (cnt) { + uint8_t this_len = cnt; + + /* Wait for some space in the fifo */ + while (!ao_radio_abort && (fifo_space = ao_radio_tx_fifo_space()) == 0) { + trace_add(trace_line, __LINE__, this_len, "wait for space"); + ao_radio_wait_fifo(); + } + if (ao_radio_abort || ao_radio_done) + break; + trace_add(trace_line, __LINE__, fifo_space, "got space"); + if (this_len > fifo_space) + this_len = fifo_space; + + cnt -= this_len; + + ao_radio_done = 0; + ao_radio_fifo = 0; + ao_radio_fifo_write(b, this_len); + b += this_len; + + ao_exti_enable(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN); + ao_exti_enable(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN); + + if (!started) { + ao_radio_pa_on(); + ao_radio_strobe(CC115L_STX); + started = 1; + } + } + if (ao_radio_abort || ao_radio_done) + break; + } + if (ao_radio_abort) + ao_radio_idle(); + ao_radio_wait_done(); + ao_radio_pa_off(); +} + +void +ao_radio_send_aprs(ao_radio_fill_func fill) +{ + ao_radio_get(); + _ao_radio_send_lots(fill, AO_RADIO_MODE_APRS); + ao_radio_put(); +} + +#if CC115L_DEBUG +const static char *cc115l_state_name[] = { + [CC115L_STATUS_STATE_IDLE] = "IDLE", + [CC115L_STATUS_STATE_TX] = "TX", + [CC115L_STATUS_STATE_FSTXON] = "FSTXON", + [CC115L_STATUS_STATE_CALIBRATE] = "CALIBRATE", + [CC115L_STATUS_STATE_SETTLING] = "SETTLING", + [CC115L_STATUS_STATE_TX_FIFO_UNDERFLOW] = "TX_FIFO_UNDERFLOW", +}; + +const static struct ao_cc115l_reg ao_cc115l_reg[] = { + { .addr = CC115L_IOCFG2, .name = "IOCFG2" }, + { .addr = CC115L_IOCFG1, .name = "IOCFG1" }, + { .addr = CC115L_IOCFG0, .name = "IOCFG0" }, + { .addr = CC115L_FIFOTHR, .name = "FIFOTHR" }, + { .addr = CC115L_SYNC1, .name = "SYNC1" }, + { .addr = CC115L_SYNC0, .name = "SYNC0" }, + { .addr = CC115L_PKTLEN, .name = "PKTLEN" }, + { .addr = CC115L_PKTCTRL0, .name = "PKTCTRL0" }, + { .addr = CC115L_CHANNR, .name = "CHANNR" }, + { .addr = CC115L_FSCTRL0, .name = "FSCTRL0" }, + { .addr = CC115L_FREQ2, .name = "FREQ2" }, + { .addr = CC115L_FREQ1, .name = "FREQ1" }, + { .addr = CC115L_FREQ0, .name = "FREQ0" }, + { .addr = CC115L_MDMCFG4, .name = "MDMCFG4" }, + { .addr = CC115L_MDMCFG3, .name = "MDMCFG3" }, + { .addr = CC115L_MDMCFG2, .name = "MDMCFG2" }, + { .addr = CC115L_MDMCFG1, .name = "MDMCFG1" }, + { .addr = CC115L_MDMCFG0, .name = "MDMCFG0" }, + { .addr = CC115L_DEVIATN, .name = "DEVIATN" }, + { .addr = CC115L_MCSM1, .name = "MCSM1" }, + { .addr = CC115L_MCSM0, .name = "MCSM0" }, + { .addr = CC115L_RESERVED_0X20, .name = "RESERVED_0X20" }, + { .addr = CC115L_FREND0, .name = "FREND0" }, + { .addr = CC115L_FSCAL3, .name = "FSCAL3" }, + { .addr = CC115L_FSCAL2, .name = "FSCAL2" }, + { .addr = CC115L_FSCAL1, .name = "FSCAL1" }, + { .addr = CC115L_FSCAL0, .name = "FSCAL0" }, + { .addr = CC115L_RESERVED_0X29, .name = "RESERVED_0X29" }, + { .addr = CC115L_RESERVED_0X2A, .name = "RESERVED_0X2A" }, + { .addr = CC115L_RESERVED_0X2B, .name = "RESERVED_0X2B" }, + { .addr = CC115L_TEST2, .name = "TEST2" }, + { .addr = CC115L_TEST1, .name = "TEST1" }, + { .addr = CC115L_TEST0, .name = "TEST0" }, + { .addr = CC115L_PARTNUM, .name = "PARTNUM" }, + { .addr = CC115L_VERSION, .name = "VERSION" }, + { .addr = CC115L_MARCSTATE, .name = "MARCSTATE" }, + { .addr = CC115L_PKTSTATUS, .name = "PKTSTATUS" }, + { .addr = CC115L_TXBYTES, .name = "TXBYTES" }, + { .addr = CC115L_PA, .name = "PA" }, +}; + +#define AO_NUM_CC115L_REG (sizeof ao_cc115l_reg / sizeof ao_cc115l_reg[0]) + +static void ao_radio_show(void) { + uint8_t status = ao_radio_status(); + int i; + + ao_radio_get(); + status = ao_radio_status(); + printf ("Status: %02x\n", status); + printf ("CHIP_RDY: %d\n", (status >> CC115L_STATUS_CHIP_RDY) & 1); + printf ("STATE: %s\n", cc115l_state_name[(status >> CC115L_STATUS_STATE) & CC115L_STATUS_STATE_MASK]); + printf ("MARC: %02x\n", ao_radio_get_marcstate()); + + for (i = 0; i < AO_NUM_CC115L_REG; i++) + printf ("\t%02x %-20.20s\n", ao_radio_reg_read(ao_cc115l_reg[i].addr), ao_cc115l_reg[i].name); + ao_radio_put(); +} + +static void ao_radio_beep(void) { + ao_radio_rdf(); +} + +static void ao_radio_packet(void) { + static const uint8_t packet[] = { +#if 1 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, +#else + 3, 1, 2, 3 +#endif + }; + + ao_radio_send(packet, sizeof (packet)); +} + +#endif /* CC115L_DEBUG */ + +#if HAS_APRS +#include <ao_aprs.h> + +static void +ao_radio_aprs() +{ +#if PACKET_HAS_SLAVE + ao_packet_slave_stop(); +#endif + ao_aprs_send(); +} +#endif + +static const struct ao_cmds ao_radio_cmds[] = { + { ao_radio_test_cmd, "C <1 start, 0 stop, none both>\0Radio carrier test" }, +#if CC115L_DEBUG +#if HAS_APRS + { ao_radio_aprs, "G\0Send APRS packet" }, +#endif + { ao_radio_show, "R\0Show CC115L status" }, + { ao_radio_beep, "b\0Emit an RDF beacon" }, + { ao_radio_packet, "p\0Send a test packet" }, +#endif + { 0, NULL } +}; + +void +ao_radio_init(void) +{ + int i; + + ao_radio_configured = 0; + ao_spi_init_cs (AO_CC115L_SPI_CS_PORT, (1 << AO_CC115L_SPI_CS_PIN)); + +#if 0 + AO_CC115L_SPI_CS_PORT->bsrr = ((uint32_t) (1 << AO_CC115L_SPI_CS_PIN)); + for (i = 0; i < 10000; i++) { + if ((SPI_2_PORT->idr & (1 << SPI_2_MISO_PIN)) == 0) + break; + } + AO_CC115L_SPI_CS_PORT->bsrr = (1 << AO_CC115L_SPI_CS_PIN); + if (i == 10000) + ao_panic(AO_PANIC_SELF_TEST_CC115L); +#endif + + /* Enable the fifo threhold interrupt pin */ + ao_enable_port(AO_CC115L_FIFO_INT_PORT); + ao_exti_setup(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_HIGH, + ao_radio_fifo_isr); + + /* Enable the tx done interrupt pin */ + ao_enable_port(AO_CC115L_DONE_INT_PORT); + ao_exti_setup(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_MED, + ao_radio_done_isr); + + ao_radio_pa_init(); + + ao_cmd_register(&ao_radio_cmds[0]); +} diff --git a/src/drivers/ao_cc115l.h b/src/drivers/ao_cc115l.h new file mode 100644 index 00000000..811c14aa --- /dev/null +++ b/src/drivers/ao_cc115l.h @@ -0,0 +1,226 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_CC115L_H_ +#define _AO_CC115L_H_ + +#define CC115L_BURST 6 +#define CC115L_READ 7 + +/* Register space */ +#define CC115L_IOCFG2 0x00 /* GDO2 Output Pin Configuration */ +#define CC115L_IOCFG1 0x01 /* GDO1 Output Pin Configuration */ +#define CC115L_IOCFG0 0x02 /* GDO0 Output Pin Configuration */ + +#define CC115L_IOCFG_GPIO1_DS 7 +#define CC115L_IOCFG_GPIO_INV 6 + +#define CC115L_IOCFG_GPIO_CFG 0 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_THR 2 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_THR_PKT 3 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_UNDERFLOW 5 +#define CC115L_IOCFG_GPIO_CFG_PKT_SYNC_TX 6 +#define CC115L_IOCFG_GPIO_CFG_PLL_LOCKED 10 +#define CC115L_IOCFG_GPIO_CFG_SERIAL_CLK 11 +#define CC115L_IOCFG_GPIO_CFG_SYNC_DATA 12 +#define CC115L_IOCFG_GPIO_CFG_ASYNC_DATA 13 +#define CC115L_IOCFG_GPIO_CFG_PA_PD 27 +#define CC115L_IOCFG_GPIO_CFG_CHIP_RDYn 41 +#define CC115L_IOCFG_GPIO_CFG_XOSC_STABLE 43 +#define CC115L_IOCFG_GPIO_CFG_HIGHZ 46 +#define CC115L_IOCFG_GPIO_CFG_HW_0 47 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_1 48 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_1_5 49 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_2 50 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_3 51 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_4 52 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_6 53 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_8 54 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_12 55 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_16 56 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_24 57 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_32 58 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_48 59 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_64 60 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_96 61 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_128 62 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_192 63 +#define CC115L_IOCFG_GPIO_CFG_MASK 0x3f + +#define CC115L_FIFOTHR 0x03 /* TX FIFO Thresholds */ +#define CC115L_FIFOTHR_THR_MASK 0x0f +#define CC115L_FIFOTHR_THR_61 0 +#define CC115L_FIFOTHR_THR_57 1 +#define CC115L_FIFOTHR_THR_53 2 +#define CC115L_FIFOTHR_THR_49 3 +#define CC115L_FIFOTHR_THR_45 4 +#define CC115L_FIFOTHR_THR_41 5 +#define CC115L_FIFOTHR_THR_37 6 +#define CC115L_FIFOTHR_THR_33 7 +#define CC115L_FIFOTHR_THR_29 8 +#define CC115L_FIFOTHR_THR_25 9 +#define CC115L_FIFOTHR_THR_21 10 +#define CC115L_FIFOTHR_THR_17 11 +#define CC115L_FIFOTHR_THR_13 12 +#define CC115L_FIFOTHR_THR_9 13 +#define CC115L_FIFOTHR_THR_5 14 +#define CC115L_FIFOTHR_THR_1 15 + +#define CC115L_SYNC1 0x04 /* Sync Word, High Byte */ +#define CC115L_SYNC0 0x05 /* Sync Word, Low Byte */ +#define CC115L_PKTLEN 0x06 /* Packet Length */ +#define CC115L_PKTCTRL0 0x08 /* Packet Automation Control */ +#define CC115L_PKTCTRL0_PKT_FORMAT 4 +#define CC115L_PKTCTRL0_PKT_FORMAT_NORMAL 0 +#define CC115L_PKTCTRL0_PKT_FORMAT_SYNC_SERIAL 1 +#define CC115L_PKTCTRL0_PKT_FORMAT_RANDOM 2 +#define CC115L_PKTCTRL0_PKT_FORMAT_ASYNC_SERIAL 3 +#define CC115L_PKTCTRL0_PKT_FORMAT_MASK 3 +#define CC115L_PKTCTRL0_PKT_CRC_EN 2 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG 0 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_FIXED 0 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_VARIABLE 1 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_INFINITE 2 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_MASK 3 +#define CC115L_CHANNR 0x0a /* Channel Number */ +#define CC115L_FSCTRL0 0x0c /* Frequency Synthesizer Control */ +#define CC115L_FREQ2 0x0d /* Frequency Control Word, High Byte */ +#define CC115L_FREQ1 0x0e /* Frequency Control Word, Middle Byte */ +#define CC115L_FREQ0 0x0f /* Frequency Control Word, Low Byte */ +#define CC115L_MDMCFG4 0x10 /* Modem Configuration */ +#define CC115L_MDMCFG4_DRATE_E 0 +#define CC115L_MDMCFG3 0x11 /* Modem Configuration */ +#define CC115L_MDMCFG2 0x12 /* Modem Configuration */ +#define CC115L_MDMCFG2_MOD_FORMAT 4 +#define CC115L_MDMCFG2_MOD_FORMAT_2FSK 0 +#define CC115L_MDMCFG2_MOD_FORMAT_GFSK 1 +#define CC115L_MDMCFG2_MOD_FORMAT_OOK 3 +#define CC115L_MDMCFG2_MOD_FORMAT_4FSK 4 +#define CC115L_MDMCFG2_MOD_FORMAT_MASK 7 +#define CC115L_MDMCFG2_MANCHESTER_EN 3 +#define CC115L_MDMCFG2_SYNC_MODE 0 +#define CC115L_MDMCFG2_SYNC_MODE_NONE 0 +#define CC115L_MDMCFG2_SYNC_MODE_16BITS 1 +#define CC115L_MDMCFG2_SYNC_MODE_32BITS 3 +#define CC115L_MDMCFG2_SYNC_MODE_MASK 3 +#define CC115L_MDMCFG1 0x13 /* Modem Configuration */ +#define CC115L_MDMCFG1_NUM_PREAMBLE 4 +#define CC115L_MDMCFG1_NUM_PREAMBLE_2 0 +#define CC115L_MDMCFG1_NUM_PREAMBLE_3 1 +#define CC115L_MDMCFG1_NUM_PREAMBLE_4 2 +#define CC115L_MDMCFG1_NUM_PREAMBLE_6 3 +#define CC115L_MDMCFG1_NUM_PREAMBLE_8 4 +#define CC115L_MDMCFG1_NUM_PREAMBLE_12 5 +#define CC115L_MDMCFG1_NUM_PREAMBLE_16 6 +#define CC115L_MDMCFG1_NUM_PREAMBLE_24 7 +#define CC115L_MDMCFG1_NUM_PREAMBLE_MASK 7 +#define CC115L_MDMCFG1_CHANSPC_E 0 +#define CC115L_MDMCFG0 0x14 /* Modem Configuration */ +#define CC115L_DEVIATN 0x15 /* Modem Deviation Setting */ +#define CC115L_DEVIATN_DEVIATION_E 4 +#define CC115L_DEVIATN_DEVIATION_E_MASK 7 +#define CC115L_DEVIATN_DEVIATION_M 0 +#define CC115L_DEVIATN_DEVIATION_M_MASK 7 +#define CC115L_MCSM1 0x17 /* Main Radio Control State Machine Configuration */ +#define CC115L_MCSM1_TXOFF_MODE 0 +#define CC115L_MCSM1_TXOFF_MODE_IDLE 0 +#define CC115L_MCSM1_TXOFF_MODE_FSTXON 1 +#define CC115L_MCSM1_TXOFF_MODE_TX 2 +#define CC115L_MCSM1_TXOFF_MODE_MASK 3 +#define CC115L_MCSM0 0x18 /* Main Radio Control State Machine Configuration */ +#define CC115L_MCSM0_FS_AUTOCAL 4 +#define CC115L_MCSM0_FS_AUTOCAL_NEVER 0 +#define CC115L_MCSM0_FS_AUTOCAL_IDLE_TO_TX 1 +#define CC115L_MCSM0_FS_AUTOCAL_TX_TO_IDLE 2 +#define CC115L_MCSM0_FS_AUTOCAL_4TH_TX_TO_IDLE 3 +#define CC115L_MCSM0_FS_AUTOCAL_MASK 3 +#define CC115L_MCSM0_PO_TIMEOUT 2 +#define CC115L_MCSM0_PO_TIMEOUT_1 0 +#define CC115L_MCSM0_PO_TIMEOUT_16 1 +#define CC115L_MCSM0_PO_TIMEOUT_64 2 +#define CC115L_MCSM0_PO_TIMEOUT_256 3 +#define CC115L_MCSM0_PO_TIMEOUT_MASK 3 +#define CC115L_MCSM0_XOSC_FORCE_ON 0 +#define CC115L_RESERVED_0X20 0x20 /* Use setting from SmartRF Studio */ +#define CC115L_FREND0 0x22 /* Front End TX Configuration */ +#define CC115L_FSCAL3 0x23 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL2 0x24 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL1 0x25 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL0 0x26 /* Frequency Synthesizer Calibration */ +#define CC115L_RESERVED_0X29 0x29 /* Use setting from SmartRF Studio */ +#define CC115L_RESERVED_0X2A 0x2a /* Use setting from SmartRF Studio */ +#define CC115L_RESERVED_0X2B 0x2b /* Use setting from SmartRF Studio */ +#define CC115L_TEST2 0x2c /* Various Test Settings */ +#define CC115L_TEST1 0x2d /* Various Test Settings */ +#define CC115L_TEST0 0x2e /* Various Test Settings */ + +/* Status registers (use BURST bit to select these) */ +#define CC115L_PARTNUM (0x30|(1<<CC115L_BURST)) /* Part number for CC115L */ +#define CC115L_VERSION (0x31|(1<<CC115L_BURST)) /* Current version number */ +#define CC115L_MARCSTATE (0x35|(1<<CC115L_BURST)) /* Control state machine state */ +#define CC115L_MARCSTATE_MASK 0x1f +#define CC115L_MARCSTATE_SLEEP 0x00 +#define CC115L_MARCSTATE_IDLE 0x01 +#define CC115L_MARCSTATE_XOFF 0x02 +#define CC115L_MARCSTATE_VCOON_MC 0x03 +#define CC115L_MARCSTATE_REGON_MC 0x04 +#define CC115L_MARCSTATE_MANCAL 0x05 +#define CC115L_MARCSTATE_VCOON 0x06 +#define CC115L_MARCSTATE_REGON 0x07 +#define CC115L_MARCSTATE_STARTCAL 0x08 +#define CC115L_MARCSTATE_BWBOOST 0x09 +#define CC115L_MARCSTATE_FS_LOCK 0x0a +#define CC115L_MARCSTATE_ENDCAL 0x0c +#define CC115L_MARCSTATE_FSTXON 0x12 +#define CC115L_MARCSTATE_TX 0x13 +#define CC115L_MARCSTATE_TX_END 0x14 +#define CC115L_MARCSTATE_TX_UNDERFLOW 0x16 +#define CC115L_PKTSTATUS (0x38|(1<<CC115L_BURST)) /* Current GDOx status and packet status */ +#define CC115L_TXBYTES (0x3a|(1<<CC115L_BURST)) /* Underflow and number of bytes in the TX FIFO */ +#define CC115L_TXBYTES_TXFIFO_UNDERFLOW 7 +#define CC115L_TXBYTES_NUM_TX_BYTES 0 +#define CC115L_TXBYTES_NUM_TX_BYTES_MASK 0x7f + +/* Command strobes (no BURST bit for these) */ +#define CC115L_SRES 0x30 +#define CC115L_SFSTXON 0x31 +#define CC115L_SXOFF 0x32 +#define CC115L_SCAL 0x33 +#define CC115L_STX 0x35 +#define CC115L_SIDLE 0x36 +#define CC115L_SPWD 0x39 +#define CC115L_SFTX 0x3b +#define CC115L_SNOP 0x3d + +#define CC115L_PA 0x3e +#define CC115L_FIFO 0x3f + +#define CC115L_FIFO_SIZE 64 + +/* Status byte */ +#define CC115L_STATUS_CHIP_RDY 7 +#define CC115L_STATUS_STATE 4 +#define CC115L_STATUS_STATE_IDLE 0 +#define CC115L_STATUS_STATE_TX 2 +#define CC115L_STATUS_STATE_FSTXON 3 +#define CC115L_STATUS_STATE_CALIBRATE 4 +#define CC115L_STATUS_STATE_SETTLING 5 +#define CC115L_STATUS_STATE_TX_FIFO_UNDERFLOW 7 +#define CC115L_STATUS_STATE_MASK 7 + +#define CC115L_STATUS_FIFO_BYTES_AVAILABLE 0 +#endif /* _AO_CC115L_H_ */ diff --git a/src/drivers/ao_companion.c b/src/drivers/ao_companion.c index 0ebe8429..0f405253 100644 --- a/src/drivers/ao_companion.c +++ b/src/drivers/ao_companion.c @@ -18,7 +18,7 @@ #include <ao.h> #include <ao_companion.h> -#ifdef MEGAMETRUM +#ifdef TELEMEGA #define ao_spi_slow(b) #define ao_spi_fast(b) #endif diff --git a/src/drivers/ao_fat.c b/src/drivers/ao_fat.c new file mode 100644 index 00000000..afd645cd --- /dev/null +++ b/src/drivers/ao_fat.c @@ -0,0 +1,1654 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef AO_FAT_TEST +#include "ao.h" +#endif + +#include "ao_fat.h" +#include "ao_bufio.h" + +/* Include FAT commands */ +#ifndef AO_FAT_TEST +#define FAT_COMMANDS 1 +#endif + +/* Spew FAT tracing */ +#define FAT_TRACE 0 + +#ifdef DBG +#undef DBG +#endif + +#if FAT_TRACE +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +/* + * Basic file system types + */ + +typedef ao_fat_offset_t offset_t; +typedef ao_fat_sector_t sector_t; +typedef ao_fat_cluster_t cluster_t; +typedef ao_fat_dirent_t dirent_t; +typedef ao_fat_cluster_offset_t cluster_offset_t; + +/* Global FAT lock */ +static uint8_t ao_fat_mutex; + +/* Partition information, sector numbers */ + +static uint8_t partition_type; +static sector_t partition_start, partition_end; + +#define AO_FAT_BAD_CLUSTER 0xffffff7 +#define AO_FAT_LAST_CLUSTER 0xfffffff +#define AO_FAT_IS_LAST_CLUSTER(c) (((c) & 0xffffff8) == 0xffffff8) +#define AO_FAT_IS_LAST_CLUSTER16(c) (((c) & 0xfff8) == 0xfff8) + + +#define SECTOR_SIZE 512 +#define SECTOR_MASK (SECTOR_SIZE - 1) +#define SECTOR_SHIFT 9 + +#define DIRENT_SIZE 32 + +/* File system parameters */ +static uint8_t sectors_per_cluster; +static uint32_t bytes_per_cluster; +static sector_t reserved_sector_count; +static uint8_t number_fat; +static dirent_t root_entries; +static sector_t sectors_per_fat; +static cluster_t number_cluster; +static sector_t fat_start; +static sector_t root_start; +static sector_t data_start; +static cluster_t next_free; +static uint8_t filesystem_full; + +/* FAT32 extra data */ +static uint8_t fat32; +static uint8_t fsinfo_dirty; +static cluster_t root_cluster; +static sector_t fsinfo_sector; +static cluster_t free_count; + +/* + * Deal with LSB FAT data structures + */ +static uint32_t +get_u32(uint8_t *base) +{ + return ((uint32_t) base[0] | + ((uint32_t) base[1] << 8) | + ((uint32_t) base[2] << 16) | + ((uint32_t) base[3] << 24)); +} + +static void +put_u32(uint8_t *base, uint32_t value) +{ + base[0] = value; + base[1] = value >> 8; + base[2] = value >> 16; + base[3] = value >> 24; +} + +static uint16_t +get_u16(uint8_t *base) +{ + return ((uint16_t) base[0] | ((uint16_t) base[1] << 8)); +} + +static void +put_u16(uint8_t *base, uint16_t value) +{ + base[0] = value; + base[1] = value >> 8; +} + +static uint8_t +_ao_fat_cluster_valid(cluster_t cluster) +{ + return (2 <= cluster && cluster < number_cluster); +} + +/* Start using a sector */ +static uint8_t * +_ao_fat_sector_get(sector_t sector) +{ + sector += partition_start; + if (sector >= partition_end) + return NULL; + return ao_bufio_get(sector); +} + +/* Finish using a sector, 'w' is 1 if modified */ +#define _ao_fat_sector_put(b,w) ao_bufio_put(b,w) + +/* Get the next cluster entry in the chain */ +static cluster_t +_ao_fat_entry_read(cluster_t cluster) +{ + sector_t sector; + cluster_t offset; + uint8_t *buf; + cluster_t ret; + + if (!_ao_fat_cluster_valid(cluster)) + return 0xfffffff7; + + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> (SECTOR_SHIFT); + offset = cluster & SECTOR_MASK; + buf = _ao_fat_sector_get(fat_start + sector); + if (!buf) + return 0; + + if (fat32) { + ret = get_u32(buf + offset); + ret &= 0xfffffff; + } else { + ret = get_u16(buf + offset); + if (AO_FAT_IS_LAST_CLUSTER16(ret)) + ret |= 0xfff0000; + } + _ao_fat_sector_put(buf, 0); + return ret; +} + +/* Replace the referenced cluster entry in the chain with + * 'new_value'. Return the previous value. + */ +static cluster_t +_ao_fat_entry_replace(cluster_t cluster, cluster_t new_value) +{ + sector_t sector; + cluster_offset_t offset; + uint8_t *buf; + cluster_t ret; + cluster_t old_value; + uint8_t fat; + + if (!_ao_fat_cluster_valid(cluster)) + return 0xfffffff7; + + /* Convert from cluster index to byte index */ + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> SECTOR_SHIFT; + offset = cluster & SECTOR_MASK; + + new_value &= 0xfffffff; + for (fat = 0; fat < number_fat; fat++) { + buf = _ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector); + if (!buf) + return 0; + if (fat32) { + old_value = get_u32(buf + offset); + put_u32(buf + offset, new_value | (old_value & 0xf0000000)); + if (fat == 0) { + ret = old_value & 0xfffffff; + + /* Track the free count if it wasn't marked + * invalid when we mounted the file system + */ + if (free_count != 0xffffffff) { + if (new_value && !ret) { + --free_count; + fsinfo_dirty = 1; + } else if (!new_value && ret) { + ++free_count; + fsinfo_dirty = 1; + } + } + } + } else { + if (fat == 0) { + ret = get_u16(buf + offset); + if (AO_FAT_IS_LAST_CLUSTER16(ret)) + ret |= 0xfff0000; + } + put_u16(buf + offset, new_value); + } + _ao_fat_sector_put(buf, 1); + } + return ret; + +} + +/* + * Walk a cluster chain and mark + * all of them as free + */ +static void +_ao_fat_free_cluster_chain(cluster_t cluster) +{ + while (_ao_fat_cluster_valid(cluster)) { + if (cluster < next_free) { + next_free = cluster; + fsinfo_dirty = 1; + } + cluster = _ao_fat_entry_replace(cluster, 0x00000000); + } +} + +/* + * _ao_fat_cluster_seek + * + * The basic file system operation -- map a file cluster index to a + * partition cluster number. Done by computing the cluster number and + * then walking that many clusters from the first cluster. Returns + * 0xffff if we walk off the end of the file or the cluster chain + * is damaged somehow + */ +static cluster_t +_ao_fat_cluster_seek(cluster_t cluster, cluster_t distance) +{ + while (distance) { + cluster = _ao_fat_entry_read(cluster); + if (!_ao_fat_cluster_valid(cluster)) + break; + distance--; + } + return cluster; +} + +/* + * _ao_fat_cluster_set_size + * + * Set the number of clusters in the specified chain, + * freeing extra ones or alocating new ones as needed + * + * Returns AO_FAT_BAD_CLUSTER on allocation failure + */ + +static cluster_t +_ao_fat_cluster_set_size(cluster_t first_cluster, cluster_t size) +{ + cluster_t have; + cluster_t last_cluster; + cluster_t next_cluster; + + /* Walk the cluster chain to the + * spot where it needs to change. That + * will either be the end of the chain (in case it needs to grow), + * or after the desired number of clusters, in which case it needs to shrink + */ + next_cluster = first_cluster; + last_cluster = 0; + DBG("\tclusters:"); + for (have = 0; have < size; have++) { + DBG(" %08x", next_cluster); + if (!_ao_fat_cluster_valid(next_cluster)) + break; + last_cluster = next_cluster; + next_cluster = _ao_fat_entry_read(next_cluster); + } + DBG("\n"); + + /* At this point, last_cluster points to the last valid + * cluster in the file, if any. That's the spot in the FAT + * that needs to be rewritten, either to truncate the file by + * writing an END marker, or to extend the file by writing + * more clusters. next_cluster will contain the value of the + * FAT at last_cluster. + * + * If this is at the head of the cluster chain, then + * last_cluster will be zero and next_cluster will + * be the first cluster in the chain. + */ + if (have == size) { + /* The file is large enough, truncate as needed */ + if (_ao_fat_cluster_valid(next_cluster)) { + DBG("truncate between %08x and %08x\n", last_cluster, next_cluster); + if (last_cluster) + /* + * Otherwise, rewrite the last cluster + * in the chain with a LAST marker + */ + (void) _ao_fat_entry_replace(last_cluster, + AO_FAT_LAST_CLUSTER); + else + /* + * If the file is getting erased, then + * rewrite the directory entry cluster + * value + */ + first_cluster = 0; + + /* Clear the remaining clusters in the chain */ + _ao_fat_free_cluster_chain(next_cluster); + + /* The file system is no longer full (if it was) */ + filesystem_full = 0; + } else { + DBG("unchanged FAT chain\n"); + /* The chain is already the right length, don't mess with it */ + ; + } + } else { + cluster_t need; + cluster_t free; + + if (filesystem_full) + return AO_FAT_BAD_CLUSTER; + + /* Set next_free if it has wrapped or wasn't set before */ + if (next_free < 2 || number_cluster <= next_free) { + next_free = 2; + fsinfo_dirty = 1; + } + + /* See if there are enough free clusters in the file system */ + need = size - have; + +#define loop_cluster for (free = next_free; need > 0;) +#define next_cluster \ + if (++free == number_cluster) \ + free = 2; \ + if (free == next_free) \ + break; \ + + loop_cluster { + if (!_ao_fat_entry_read(free)) + need--; + next_cluster; + } + + /* Still need some, tell the user that we've failed */ + if (need) { + filesystem_full = 1; + return AO_FAT_BAD_CLUSTER; + } + + /* Now go allocate those clusters and + * thread them onto the chain + */ + need = size - have; + loop_cluster { + if (_ao_fat_entry_read(free) == 0) { + next_free = free + 1; + if (next_free >= number_cluster) + next_free = 2; + fsinfo_dirty = 1; + DBG("\tadd cluster. old %08x new %08x\n", last_cluster, free); + if (last_cluster) + _ao_fat_entry_replace(last_cluster, free); + else + first_cluster = free; + last_cluster = free; + need--; + } + next_cluster; + } +#undef loop_cluster +#undef next_cluster + DBG("\tlast cluster %08x\n", last_cluster); + /* Mark the new end of the chain */ + _ao_fat_entry_replace(last_cluster, AO_FAT_LAST_CLUSTER); + } + + DBG("\tfirst cluster %08x\n", first_cluster); + return first_cluster; +} + +/* Start using a root directory entry */ +static uint8_t * +_ao_fat_root_get(dirent_t e) +{ + offset_t byte = e * DIRENT_SIZE; + sector_t sector = byte >> SECTOR_SHIFT; + cluster_offset_t offset = byte & SECTOR_MASK; + uint8_t *buf; + + if (fat32) { + cluster_t cluster_distance = sector / sectors_per_cluster; + sector_t sector_index = sector % sectors_per_cluster; + cluster_t cluster = _ao_fat_cluster_seek(root_cluster, cluster_distance); + + if (_ao_fat_cluster_valid(cluster)) + sector = data_start + (cluster-2) * sectors_per_cluster + sector_index; + else + return NULL; + } else { + if (e >= root_entries) + return NULL; + sector = root_start + sector; + } + + buf = _ao_fat_sector_get(sector); + if (!buf) + return NULL; + return buf + offset; +} + +/* Finish using a root directory entry, 'w' is 1 if modified */ +static void +_ao_fat_root_put(uint8_t *root, dirent_t e, uint8_t write) +{ + cluster_offset_t offset = ((e * DIRENT_SIZE) & SECTOR_MASK); + uint8_t *buf = root - offset; + + _ao_fat_sector_put(buf, write); +} + +/* + * _ao_fat_root_extend + * + * On FAT32, make the root directory at least 'ents' entries long + */ +static int8_t +_ao_fat_root_extend(dirent_t ents) +{ + offset_t byte_size; + cluster_t cluster_size; + if (!fat32) + return 0; + + byte_size = (ents + 1) * 0x20; + cluster_size = (byte_size + bytes_per_cluster - 1) / bytes_per_cluster; + if (_ao_fat_cluster_set_size(root_cluster, cluster_size) != AO_FAT_BAD_CLUSTER) + return 1; + return 0; +} + +/* + * _ao_fat_setup_partition + * + * Load the boot block and find the first partition + */ +static uint8_t +_ao_fat_setup_partition(void) +{ + uint8_t *mbr; + uint8_t *partition; + uint32_t partition_size; + + mbr = ao_bufio_get(0); + if (!mbr) + return AO_FAT_FILESYSTEM_MBR_READ_FAILURE; + + /* Check the signature */ + if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) { + DBG ("Invalid MBR signature %02x %02x\n", + mbr[0x1fe], mbr[0x1ff]); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE; + } + + /* Check to see if it's actually a boot block, in which + * case it's presumably not a paritioned device + */ + + if (mbr[0] == 0xeb) { + partition_start = 0; + partition_size = get_u16(mbr + 0x13); + if (partition_size == 0) + partition_size = get_u32(mbr + 0x20); + } else { + /* Just use the first partition */ + partition = &mbr[0x1be]; + + partition_type = partition[4]; + switch (partition_type) { + case 4: /* FAT16 up to 32M */ + case 6: /* FAT16 over 32M */ + break; + case 0x0b: /* FAT32 up to 2047GB */ + case 0x0c: /* FAT32 LBA */ + break; + default: + DBG ("Invalid partition type %02x\n", partition_type); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE; + } + + partition_start = get_u32(partition+8); + partition_size = get_u32(partition+12); + if (partition_size == 0) { + DBG ("Zero-sized partition\n"); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION; + } + } + partition_end = partition_start + partition_size; + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_SUCCESS; +} + +static uint8_t +_ao_fat_setup_fs(void) +{ + uint8_t *boot = _ao_fat_sector_get(0); + uint32_t data_sectors; + + if (!boot) + return AO_FAT_FILESYSTEM_BOOT_READ_FAILURE; + + /* Check the signature */ + if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) { + DBG ("Invalid BOOT signature %02x %02x\n", + boot[0x1fe], boot[0x1ff]); + _ao_fat_sector_put(boot, 0); + return AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE; + } + + /* Check the sector size */ + if (get_u16(boot + 0xb) != SECTOR_SIZE) { + DBG ("Invalid sector size %d\n", + get_u16(boot + 0xb)); + _ao_fat_sector_put(boot, 0); + return AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE; + } + + sectors_per_cluster = boot[0xd]; + bytes_per_cluster = sectors_per_cluster << SECTOR_SHIFT; + reserved_sector_count = get_u16(boot+0xe); + number_fat = boot[0x10]; + root_entries = get_u16(boot + 0x11); + sectors_per_fat = get_u16(boot+0x16); + fat32 = 0; + if (sectors_per_fat == 0) { + fat32 = 1; + sectors_per_fat = get_u32(boot+0x24); + root_cluster = get_u32(boot+0x2c); + fsinfo_sector = get_u16(boot + 0x30); + } + _ao_fat_sector_put(boot, 0); + + free_count = 0xffffffff; + next_free = 0; + if (fat32 && fsinfo_sector) { + uint8_t *fsinfo = _ao_fat_sector_get(fsinfo_sector); + + if (fsinfo) { + free_count = get_u32(fsinfo + 0x1e8); + next_free = get_u32(fsinfo + 0x1ec); + _ao_fat_sector_put(fsinfo, 0); + } + } + + fat_start = reserved_sector_count;; + root_start = fat_start + number_fat * sectors_per_fat; + data_start = root_start + ((root_entries * DIRENT_SIZE + SECTOR_MASK) >> SECTOR_SHIFT); + + data_sectors = (partition_end - partition_start) - data_start; + + number_cluster = data_sectors / sectors_per_cluster; + + return AO_FAT_FILESYSTEM_SUCCESS; +} + +/* + * State for an open file + */ + +struct ao_file { + struct ao_fat_dirent *dirent; + offset_t offset; + offset_t cluster_offset; + cluster_t cluster; + uint8_t busy; +}; + +#define AO_FAT_NFILE 8 + +static struct ao_fat_dirent ao_file_dirent[AO_FAT_NFILE]; + +static struct ao_fat_dirent * +_ao_fat_file_dirent_alloc(struct ao_fat_dirent *want) +{ + int8_t d; + struct ao_fat_dirent *free = NULL, *dirent; + + for (d = 0; d < AO_FAT_NFILE; d++) { + + dirent = &ao_file_dirent[d]; + /* See if there's another user of this file already */ + if (want && dirent->name[0] != 0) { + if (dirent->entry == want->entry) + return dirent; + } else { + if (!free) { + free = dirent; + if (!want) + break; + } + } + } + if (free && want) + *free = *want; + return free; +} + +static struct ao_file ao_file_table[AO_FAT_NFILE]; + +static int8_t +_ao_fat_fd_alloc(struct ao_fat_dirent *dirent) +{ + int8_t fd; + + for (fd = 0; fd < AO_FAT_NFILE; fd++) + if (!ao_file_table[fd].busy) { + ao_file_table[fd].dirent = _ao_fat_file_dirent_alloc(dirent); + ao_file_table[fd].busy = 1; + ao_file_table[fd].offset = 0; + ao_file_table[fd].cluster_offset = 0; + ao_file_table[fd].cluster = ao_file_table[fd].dirent->cluster; + + return fd; + } + return -AO_FAT_EMFILE; +} + +static void +_ao_fat_fd_free(int8_t fd) +{ + struct ao_file *file = &ao_file_table[fd]; + struct ao_fat_dirent *dirent = file->dirent; + memset(&ao_file_table[fd], '\0', sizeof (struct ao_file)); + + /* Check and see if another ao_file references the same dirent */ + for (fd = 0; fd < AO_FAT_NFILE; fd++) + if (ao_file_table[fd].dirent == dirent) + return; + memset(dirent, '\0', sizeof (struct ao_fat_dirent)); +} + +static struct ao_file * +_ao_fat_fd_to_file(int8_t fd) +{ + struct ao_file *file; + if (fd < 0 || AO_FAT_NFILE <= fd) + return NULL; + + file = &ao_file_table[fd]; + if (!file->busy) + return NULL; + return file; +} + +static uint8_t ao_filesystem_setup; +static uint8_t ao_filesystem_status; + +static uint8_t +_ao_fat_setup(void) +{ + if (!ao_filesystem_setup) { + + ao_filesystem_setup = 1; + ao_bufio_setup(); + + /* Re-initialize all global state; this will help to allow the + * file system to get swapped someday + */ + partition_type = partition_start = partition_end = 0; + sectors_per_cluster = bytes_per_cluster = reserved_sector_count = 0; + number_fat = root_entries = sectors_per_fat = 0; + number_cluster = fat_start = root_start = data_start = 0; + next_free = filesystem_full = 0; + fat32 = fsinfo_dirty = root_cluster = fsinfo_sector = free_count = 0; + + /* Reset open file table */ + memset(&ao_file_table, '\0', sizeof (ao_file_table)); + + ao_filesystem_status = _ao_fat_setup_partition(); + if (ao_filesystem_status != AO_FAT_FILESYSTEM_SUCCESS) + return ao_filesystem_status; + ao_filesystem_status = _ao_fat_setup_fs(); + if (ao_filesystem_status != AO_FAT_FILESYSTEM_SUCCESS) + return ao_filesystem_status; + } + return ao_filesystem_status; +} + +void +ao_fat_unmount(void) +{ + ao_filesystem_setup = 0; +} + +/* + * Basic file operations + */ + +static uint32_t +_ao_fat_current_sector(struct ao_file *file) +{ + cluster_t cluster_offset; + uint32_t sector_offset; + uint16_t sector_index; + cluster_t cluster; + + DBG("current sector offset %d size %d\n", + file->offset, file->dirent->size); + + if (file->offset > file->dirent->size) { + printf ("file offset %d larger than size %d\n", + file->offset, file->dirent->size); + return 0xffffffff; + } + + sector_offset = file->offset >> SECTOR_SHIFT; + + if (!file->cluster || file->offset < file->cluster_offset) { + file->cluster = file->dirent->cluster; + file->cluster_offset = 0; + DBG("\treset to start of file %08x\n", file->cluster); + } + + if (file->cluster_offset + bytes_per_cluster <= file->offset) { + cluster_t cluster_distance; + + cluster_offset = sector_offset / sectors_per_cluster; + + cluster_distance = cluster_offset - file->cluster_offset / bytes_per_cluster; + + DBG("\tseek forward %d clusters\n", cluster_distance); + cluster = _ao_fat_cluster_seek(file->cluster, cluster_distance); + + if (!_ao_fat_cluster_valid(cluster)) { + printf ("invalid cluster %08x\n", cluster); + return 0xffffffff; + } + file->cluster = cluster; + file->cluster_offset = cluster_offset * bytes_per_cluster; + } + + sector_index = sector_offset % sectors_per_cluster; + DBG("current cluster %08x sector_index %d sector %d\n", + file->cluster, sector_index, + data_start + (uint32_t) (file->cluster-2) * sectors_per_cluster + sector_index); + return data_start + (uint32_t) (file->cluster-2) * sectors_per_cluster + sector_index; +} + +/* + * _ao_fat_invaldate_cluster_offset + * + * When the file size gets shrunk, invalidate + * any file structures referencing clusters beyond that point + */ + +static void +_ao_fat_invalidate_cluster_offset(struct ao_fat_dirent *dirent) +{ + int8_t fd; + struct ao_file *file; + + for (fd = 0; fd < AO_FAT_NFILE; fd++) { + file = &ao_file_table[fd]; + if (!file->busy) + continue; + if (file->dirent == dirent) { + if (file->cluster_offset >= dirent->size) { + file->cluster_offset = 0; + file->cluster = dirent->cluster; + } + } + } +} + + +/* + * _ao_fat_set_size + * + * Set the size of the current file, truncating or extending + * the cluster chain as needed + */ +static int8_t +_ao_fat_set_size(struct ao_file *file, uint32_t size) +{ + uint8_t *dent; + cluster_t first_cluster; + cluster_t have_clusters, need_clusters; + + DBG ("Set size %d\n", size); + if (size == file->dirent->size) { + DBG("\tsize match\n"); + return AO_FAT_SUCCESS; + } + + first_cluster = file->dirent->cluster; + have_clusters = (file->dirent->size + bytes_per_cluster - 1) / bytes_per_cluster; + need_clusters = (size + bytes_per_cluster - 1) / bytes_per_cluster; + + DBG ("\tfirst cluster %08x have %d need %d\n", first_cluster, have_clusters, need_clusters); + if (have_clusters != need_clusters) { + if (file->cluster && size > file->cluster_offset) { + cluster_t offset_clusters = (file->cluster_offset + bytes_per_cluster) / bytes_per_cluster; + cluster_t extra_clusters = need_clusters - offset_clusters; + cluster_t next_cluster; + + DBG ("\tset size relative offset_clusters %d extra_clusters %d\n", + offset_clusters, extra_clusters); + + /* Need one more to account for file->cluster, which we already have */ + next_cluster = _ao_fat_cluster_set_size(file->cluster, extra_clusters + 1); + if (next_cluster == AO_FAT_BAD_CLUSTER) + return -AO_FAT_ENOSPC; + } else { + DBG ("\tset size absolute need_clusters %d\n", need_clusters); + first_cluster = _ao_fat_cluster_set_size(first_cluster, need_clusters); + + if (first_cluster == AO_FAT_BAD_CLUSTER) + return -AO_FAT_ENOSPC; + } + } + + DBG ("\tupdate directory size\n"); + /* Update the directory entry */ + dent = _ao_fat_root_get(file->dirent->entry); + if (!dent) { + printf ("dent update failed\n"); + return -AO_FAT_EIO; + } + put_u32(dent + 0x1c, size); + put_u16(dent + 0x1a, first_cluster); + if (fat32) + put_u16(dent + 0x14, first_cluster >> 16); + _ao_fat_root_put(dent, file->dirent->entry, 1); + + file->dirent->size = size; + file->dirent->cluster = first_cluster; + if (have_clusters > need_clusters) + _ao_fat_invalidate_cluster_offset(file->dirent); + DBG ("set size done\n"); + return AO_FAT_SUCCESS; +} + +/* + * _ao_fat_root_init + * + * Initialize a root directory entry + */ +static void +_ao_fat_root_init(uint8_t *dent, char name[11], uint8_t attr) +{ + memset(dent, '\0', 0x20); + memmove(dent, name, 11); + + dent[0x0b] = 0x00; + dent[0x0c] = 0x00; + dent[0x0d] = 0x00; + + /* XXX fix time */ + put_u16(dent + 0x0e, 0); + /* XXX fix date */ + put_u16(dent + 0x10, 0); + /* XXX fix date */ + put_u16(dent + 0x12, 0); + + /* XXX fix time */ + put_u16(dent + 0x16, 0); + /* XXX fix date */ + put_u16(dent + 0x18, 0); + + /* cluster number */ + /* Low cluster bytes */ + put_u16(dent + 0x1a, 0); + /* FAT32 high cluster bytes */ + put_u16(dent + 0x14, 0); + + /* size */ + put_u32(dent + 0x1c, 0); +} + + +static void +_ao_fat_dirent_init(struct ao_fat_dirent *dirent, uint8_t *dent, uint16_t entry) +{ + memcpy(dirent->name, dent + 0x00, 11); + dirent->attr = dent[0x0b]; + dirent->size = get_u32(dent+0x1c); + dirent->cluster = get_u16(dent+0x1a); + if (fat32) + dirent->cluster |= (cluster_t) get_u16(dent + 0x14) << 16; + dirent->entry = entry; +} + +/* + * _ao_fat_flush_fsinfo + * + * Write out any fsinfo changes to disk + */ + +static void +_ao_fat_flush_fsinfo(void) +{ + uint8_t *fsinfo; + + if (!fat32) + return; + + if (!fsinfo_dirty) + return; + fsinfo_dirty = 0; + if (!fsinfo_sector) + return; + + fsinfo = _ao_fat_sector_get(fsinfo_sector); + if (fsinfo) { + put_u32(fsinfo + 0x1e8, free_count); + put_u32(fsinfo + 0x1ec, next_free); + _ao_fat_sector_put(fsinfo, 1); + } +} + +/* + * Public API + */ + +/* + * ao_fat_sync + * + * Flush any pending I/O to storage + */ + +static void +_ao_fat_sync(void) +{ + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return; + _ao_fat_flush_fsinfo(); + ao_bufio_flush(); +} + +void +ao_fat_sync(void) +{ + ao_mutex_get(&ao_fat_mutex); + _ao_fat_sync(); + ao_mutex_put(&ao_fat_mutex); +} + +/* + * ao_fat_full + * + * Returns TRUE if the filesystem cannot take + * more data + */ + +int8_t +ao_fat_full(void) +{ + ao_mutex_get(&ao_fat_mutex); + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) { + ao_mutex_put(&ao_fat_mutex); + return 1; + } + ao_mutex_put(&ao_fat_mutex); + return filesystem_full; +} + +static int8_t +_ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) +{ + uint8_t *dent; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + for (;;) { + dent = _ao_fat_root_get(*entry); + if (!dent) + return -AO_FAT_EDIREOF; + + if (dent[0] == AO_FAT_DENT_END) { + _ao_fat_root_put(dent, *entry, 0); + return -AO_FAT_EDIREOF; + } + if (dent[0] != AO_FAT_DENT_EMPTY && (dent[0xb] & 0xf) != 0xf) { + _ao_fat_dirent_init(dirent, dent, *entry); + _ao_fat_root_put(dent, *entry, 0); + (*entry)++; + return AO_FAT_SUCCESS; + } + _ao_fat_root_put(dent, *entry, 0); + (*entry)++; + } +} + +int8_t +ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_readdir(entry, dirent); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_open + * + * Open an existing file. + */ +static int8_t +_ao_fat_open(char name[11], uint8_t mode) +{ + uint16_t entry = 0; + struct ao_fat_dirent dirent; + int8_t status; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + for (;;) { + status = _ao_fat_readdir(&entry, &dirent); + if (status < 0) { + if (status == -AO_FAT_EDIREOF) + return -AO_FAT_ENOENT; + return status; + } + if (!memcmp(name, dirent.name, 11)) { + if (AO_FAT_IS_DIR(dirent.attr)) + return -AO_FAT_EISDIR; + if (!AO_FAT_IS_FILE(dirent.attr)) + return -AO_FAT_EPERM; + if (mode > AO_FAT_OPEN_READ && (dirent.attr & AO_FAT_FILE_READ_ONLY)) + return -AO_FAT_EACCESS; + return _ao_fat_fd_alloc(&dirent); + } + } + return -AO_FAT_ENOENT; +} + +int8_t +ao_fat_open(char name[11], uint8_t mode) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_open(name, mode); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_close + * + * Close the currently open file + */ +static int8_t +_ao_fat_close(int8_t fd) +{ + struct ao_file *file; + + file = _ao_fat_fd_to_file(fd); + if (!file) + return -AO_FAT_EBADF; + + _ao_fat_fd_free(fd); + _ao_fat_sync(); + return AO_FAT_SUCCESS; +} + +int8_t +ao_fat_close(int8_t fd) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_close(fd); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_creat + * + * Open and truncate an existing file or + * create a new file + */ + +static int8_t +_ao_fat_creat(char name[11]) +{ + uint16_t entry; + int8_t fd; + int8_t status; + uint8_t *dent; + struct ao_file *file; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + fd = _ao_fat_open(name, AO_FAT_OPEN_WRITE); + if (fd >= 0) { + file = &ao_file_table[fd]; + status = _ao_fat_set_size(file, 0); + if (status < 0) { + _ao_fat_close(fd); + fd = status; + } + } else { + if (fd == -AO_FAT_ENOENT) { + entry = 0; + for (;;) { + dent = _ao_fat_root_get(entry); + if (!dent) { + + if (_ao_fat_root_extend(entry)) + continue; + fd = -AO_FAT_ENOSPC; + break; + } + if (dent[0] == AO_FAT_DENT_EMPTY || dent[0] == AO_FAT_DENT_END) { + fd = _ao_fat_fd_alloc(NULL); + if (fd < 0) { + _ao_fat_root_put(dent, entry, 0); + break; + } + + file = &ao_file_table[fd]; + /* Initialize the dent */ + _ao_fat_root_init(dent, name, AO_FAT_FILE_REGULAR); + + /* Now initialize the dirent from the dent */ + _ao_fat_dirent_init(file->dirent, dent, entry); + + /* And write the dent to storage */ + _ao_fat_root_put(dent, entry, 1); + + status = -AO_FAT_SUCCESS; + break; + } else { + _ao_fat_root_put(dent, entry, 0); + } + entry++; + } + } + } + return fd; +} + +int8_t +ao_fat_creat(char name[11]) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_creat(name); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_map_current + * + * Map the sector pointed at by the current file offset + */ + +static void * +ao_fat_map_current(struct ao_file *file, int len, cluster_offset_t *offsetp, cluster_offset_t *this_time) +{ + cluster_offset_t offset; + sector_t sector; + void *buf; + + offset = file->offset & SECTOR_MASK; + sector = _ao_fat_current_sector(file); + if (sector == 0xffffffff) { + printf ("invalid sector at offset %d\n", file->offset); + return NULL; + } + buf = _ao_fat_sector_get(sector); + if (!buf) + printf ("sector get failed. Sector %d. Partition end %d\n", sector, partition_end); + if (offset + len < SECTOR_SIZE) + *this_time = len; + else + *this_time = SECTOR_SIZE - offset; + *offsetp = offset; + return buf; +} + +/* + * ao_fat_read + * + * Read from the file + */ +int +ao_fat_read(int8_t fd, void *dst, int len) +{ + uint8_t *dst_b = dst; + cluster_offset_t this_time; + cluster_offset_t offset; + uint8_t *buf; + int ret = 0; + struct ao_file *file; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + if (file->offset + len > file->dirent->size) + len = file->dirent->size - file->offset; + + if (len < 0) + len = 0; + + while (len) { + buf = ao_fat_map_current(file, len, &offset, &this_time); + if (!buf) { + printf ("map_current failed\n"); + ret = -AO_FAT_EIO; + break; + } + memcpy(dst_b, buf + offset, this_time); + _ao_fat_sector_put(buf, 0); + + ret += this_time; + len -= this_time; + dst_b += this_time; + file->offset = file->offset + this_time; + } +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_write + * + * Write to the file, extended as necessary + */ +int +ao_fat_write(int8_t fd, void *src, int len) +{ + uint8_t *src_b = src; + cluster_offset_t this_time; + cluster_offset_t offset; + uint8_t *buf; + int ret = 0; + struct ao_file *file; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + if (file->offset + len > file->dirent->size) { + ret = _ao_fat_set_size(file, file->offset + len); + if (ret < 0) + goto done; + } + + while (len) { + buf = ao_fat_map_current(file, len, &offset, &this_time); + if (!buf) { + printf ("map_current failed\n"); + ret = -AO_FAT_EIO; + break; + } + memcpy(buf + offset, src_b, this_time); + _ao_fat_sector_put(buf, 1); + + ret += this_time; + len -= this_time; + src_b += this_time; + file->offset = file->offset + this_time; + } +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_seek + * + * Set the position for the next I/O operation + * Note that this doesn't actually change the size + * of the file if the requested position is beyond + * the current file length, that would take a future + * write + */ +int32_t +ao_fat_seek(int8_t fd, int32_t pos, uint8_t whence) +{ + offset_t new_offset; + struct ao_file *file; + int32_t ret; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + new_offset = file->offset; + switch (whence) { + case AO_FAT_SEEK_SET: + new_offset = pos; + break; + case AO_FAT_SEEK_CUR: + new_offset += pos; + break; + case AO_FAT_SEEK_END: + new_offset = file->dirent->size + pos; + break; + } + ret = file->offset = new_offset; +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_unlink + * + * Remove a file from the directory, marking + * all clusters as free + */ +int8_t +ao_fat_unlink(char name[11]) +{ + uint16_t entry = 0; + struct ao_fat_dirent dirent; + int8_t ret; + + ao_mutex_get(&ao_fat_mutex); + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) { + ret = -AO_FAT_EIO; + goto done; + } + + while (ao_fat_readdir(&entry, &dirent)) { + if (memcmp(name, dirent.name, 11) == 0) { + uint8_t *next; + uint8_t *ent; + uint8_t delete; + + if (AO_FAT_IS_DIR(dirent.attr)) { + ret = -AO_FAT_EISDIR; + goto done; + } + if (!AO_FAT_IS_FILE(dirent.attr)) { + ret = -AO_FAT_EPERM; + goto done; + } + + _ao_fat_free_cluster_chain(dirent.cluster); + next = _ao_fat_root_get(dirent.entry + 1); + if (next && next[0] != AO_FAT_DENT_END) + delete = AO_FAT_DENT_EMPTY; + else + delete = AO_FAT_DENT_END; + if (next) + _ao_fat_root_put(next, dirent.entry + 1, 0); + ent = _ao_fat_root_get(dirent.entry); + if (ent) { + memset(ent, '\0', DIRENT_SIZE); + *ent = delete; + _ao_fat_root_put(ent, dirent.entry, 1); + } + ao_bufio_flush(); + ret = AO_FAT_SUCCESS; + goto done; + } + } + ret = -AO_FAT_ENOENT; +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +int8_t +ao_fat_rename(char old[11], char new[11]) +{ + return -AO_FAT_EIO; +} + +#if FAT_COMMANDS + +static const char *filesystem_errors[] = { + [AO_FAT_FILESYSTEM_SUCCESS] = "FAT file system operating normally", + [AO_FAT_FILESYSTEM_MBR_READ_FAILURE] = "MBR media read error", + [AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE] = "MBR signature invalid", + [AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE] = "Unsupported paritition type", + [AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION] = "Partition has zero sectors", + [AO_FAT_FILESYSTEM_BOOT_READ_FAILURE] = "Boot block media read error", + [AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE] = "Boot block signature invalid", + [AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE] = "Sector size not 512", +}; + +static void +ao_fat_mbr_cmd(void) +{ + uint8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_setup(); + if (status == AO_FAT_FILESYSTEM_SUCCESS) { + printf ("partition type: %02x\n", partition_type); + printf ("partition start: %08x\n", partition_start); + + printf ("partition end: %08x\n", partition_end); + + printf ("fat32: %d\n", fat32); + printf ("sectors per cluster %d\n", sectors_per_cluster); + printf ("reserved sectors %d\n", reserved_sector_count); + printf ("number of FATs %d\n", number_fat); + printf ("root entries %d\n", root_entries); + printf ("sectors per fat %d\n", sectors_per_fat); + + printf ("fat start %d\n", fat_start); + printf ("root start %d\n", root_start); + printf ("data start %d\n", data_start); + } else { + printf ("FAT filesystem not available: %s\n", filesystem_errors[status]); + } + ao_mutex_put(&ao_fat_mutex); +} + +struct ao_fat_attr { + uint8_t bit; + char label; +}; + +static const struct ao_fat_attr ao_fat_attr[] = { + { .bit = AO_FAT_FILE_READ_ONLY, .label = 'R' }, + { .bit = AO_FAT_FILE_HIDDEN, .label = 'H' }, + { .bit = AO_FAT_FILE_SYSTEM, .label = 'S' }, + { .bit = AO_FAT_FILE_VOLUME_LABEL, .label = 'V' }, + { .bit = AO_FAT_FILE_DIRECTORY, .label = 'D' }, + { .bit = AO_FAT_FILE_ARCHIVE, .label = 'A' }, +}; + +#define NUM_FAT_ATTR (sizeof (ao_fat_attr) / sizeof (ao_fat_attr[0])) + +static void +ao_fat_list_cmd(void) +{ + uint16_t entry = 0; + struct ao_fat_dirent dirent; + int i; + int8_t status; + + while ((status = ao_fat_readdir(&entry, &dirent)) == AO_FAT_SUCCESS) { + for (i = 0; i < 8; i++) + putchar(dirent.name[i]); + putchar('.'); + for (; i < 11; i++) + putchar(dirent.name[i]); + for (i = 0; i < NUM_FAT_ATTR; i++) + putchar (dirent.attr & ao_fat_attr[i].bit ? ao_fat_attr[i].label : ' '); + printf (" @%08x %d\n", dirent.cluster, dirent.size); + } + if (status != -AO_FAT_EDIREOF) + printf ("readdir failed: %d\n", status); +} + +static uint8_t +ao_fat_parse_name(char name[11]) +{ + uint8_t c; + + name[0] = '\0'; + ao_cmd_white(); + c = 0; + while (ao_cmd_lex_c != '\n') { + if (ao_cmd_lex_c == '.') { + for (; c < 8; c++) + name[c] = ' '; + } else { + if (c < 11) + name[c++] = ao_cmd_lex_c; + } + ao_cmd_lex(); + } + while (c < 11) + name[c++] = ' '; +} + +static void +ao_fat_dump_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[32]; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_open(name, AO_FAT_OPEN_READ); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + while ((cnt = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + for (i = 0; i < cnt; i++) + putchar(buf[i]); + } + ao_fat_close(fd); +} + +static void +ao_fat_write_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[64]; + char c; + int status; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_creat(name); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + flush(); + while ((c = getchar()) != 4) { + if (c == '\r') c = '\n'; + if (ao_echo()) { + if (c == '\n') putchar ('\r'); + putchar(c); flush(); + } + status = ao_fat_write(fd, &c, 1); + if (status != 1) { + printf ("Write failure %d\n", status); + break; + } + } + ao_fat_close(fd); +} + +static void +put32(uint32_t a) +{ + ao_cmd_put16(a >> 16); + ao_cmd_put16(a); +} + +static void +ao_fat_hexdump_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[8]; + uint32_t addr; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_open(name, AO_FAT_OPEN_READ); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + addr = 0; + while ((cnt = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + put32(addr); + for (i = 0; i < cnt; i++) { + putchar(' '); + ao_cmd_put8(buf[i]); + } + putchar('\n'); + addr += cnt; + } + ao_fat_close(fd); +} + +static const struct ao_cmds ao_fat_cmds[] = { + { ao_fat_mbr_cmd, "M\0Show FAT MBR and other info" }, + { ao_fat_list_cmd, "F\0List FAT directory" }, + { ao_fat_dump_cmd, "D <name>\0Dump FAT file" }, + { ao_fat_write_cmd, "W <name>\0Write FAT file (end with ^D)" }, + { ao_fat_hexdump_cmd, "H <name>\0HEX dump FAT file" }, + { 0, NULL }, +}; + +#endif + +void +ao_fat_init(void) +{ + ao_bufio_init(); + ao_cmd_register(&ao_fat_cmds[0]); +} diff --git a/src/drivers/ao_fat.h b/src/drivers/ao_fat.h new file mode 100644 index 00000000..01435363 --- /dev/null +++ b/src/drivers/ao_fat.h @@ -0,0 +1,141 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_FAT_H_ +#define _AO_FAT_H_ + +void +ao_fat_init(void); + +#define AO_FAT_FILE_REGULAR 0x00 +#define AO_FAT_FILE_READ_ONLY 0x01 +#define AO_FAT_FILE_HIDDEN 0x02 +#define AO_FAT_FILE_SYSTEM 0x04 +#define AO_FAT_FILE_VOLUME_LABEL 0x08 +#define AO_FAT_FILE_DIRECTORY 0x10 +#define AO_FAT_FILE_ARCHIVE 0x20 + +#define AO_FAT_DENT_EMPTY 0xe5 +#define AO_FAT_DENT_END 0x00 + +#define AO_FAT_IS_FILE(attr) (((attr) & (AO_FAT_FILE_VOLUME_LABEL|AO_FAT_FILE_DIRECTORY)) == 0) +#define AO_FAT_IS_DIR(attr) (((attr) & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == AO_FAT_FILE_DIRECTORY) + +/* API error codes */ +#define AO_FAT_SUCCESS 0 +#define AO_FAT_EPERM 1 +#define AO_FAT_ENOENT 2 +#define AO_FAT_EIO 4 +#define AO_FAT_EBADF 9 +#define AO_FAT_EACCESS 13 +#define AO_FAT_EEXIST 17 +#define AO_FAT_ENOTDIR 20 +#define AO_FAT_EISDIR 21 +#define AO_FAT_EMFILE 24 +#define AO_FAT_EFBIG 27 +#define AO_FAT_ENOSPC 28 +#define AO_FAT_EDIREOF 29 + +/* ao_fat_setup return values */ +#define AO_FAT_FILESYSTEM_SUCCESS 0 +#define AO_FAT_FILESYSTEM_MBR_READ_FAILURE 1 +#define AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE 2 +#define AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE 3 +#define AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION 4 + +#define AO_FAT_FILESYSTEM_BOOT_READ_FAILURE 5 +#define AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE 6 +#define AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE 7 + +void +ao_fat_sync(void); + +void +ao_fat_unmount(void); + +int8_t +ao_fat_full(void); + +int8_t +ao_fat_open(char name[11], uint8_t mode); + +#define AO_FAT_OPEN_READ 0 +#define AO_FAT_OPEN_WRITE 1 +#define AO_FAT_OPEN_RW 2 + +int8_t +ao_fat_creat(char name[11]); + +int8_t +ao_fat_close(int8_t fd); + +int +ao_fat_read(int8_t fd, void *dest, int len); + +int +ao_fat_write(int8_t fd, void *src, int len); + +#define AO_FAT_SEEK_SET 0 +#define AO_FAT_SEEK_CUR 1 +#define AO_FAT_SEEK_END 2 + +int32_t +ao_fat_seek(int8_t fd, int32_t pos, uint8_t whence); + +int8_t +ao_fat_unlink(char name[11]); + +int8_t +ao_fat_rename(char old[11], char new[11]); + +/* + * Byte offset within a file. Supports files up to 2GB in size + */ +typedef int32_t ao_fat_offset_t; + +/* + * Cluster index in partition data space + */ +typedef uint32_t ao_fat_cluster_t; + +/* + * Sector offset within partition + */ +typedef uint32_t ao_fat_sector_t; + +/* + * Index within the root directory + */ +typedef uint16_t ao_fat_dirent_t; + +/* + * Offset within a cluster (or sector) + */ +typedef uint16_t ao_fat_cluster_offset_t; + +struct ao_fat_dirent { + char name[11]; + uint8_t attr; + uint32_t size; + ao_fat_cluster_t cluster; + uint16_t entry; +}; + +int8_t +ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent); + +#endif /* _AO_FAT_H_ */ diff --git a/src/drivers/ao_log_fat.c b/src/drivers/ao_log_fat.c new file mode 100644 index 00000000..af77401c --- /dev/null +++ b/src/drivers/ao_log_fat.c @@ -0,0 +1,95 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" +#include "ao_log.h" +#include "ao_fat.h" + +static uint8_t log_year, log_month, log_day; +static uint8_t log_open; +static int8_t log_fd; +static uint8_t log_mutex; + +static void +ao_log_open(void) +{ + char name[12]; + int8_t status; + + sprintf(name,"%04d%02d%02dLOG", 2000 + log_year, log_month, log_day); + status = ao_fat_open(name, AO_FAT_OPEN_WRITE); + if (status >= 0) { + log_fd = status; + ao_fat_seek(log_fd, 0, AO_FAT_SEEK_END); + log_open = 1; + } else if (status == -AO_FAT_ENOENT) { + status = ao_fat_creat(name); + if (status == AO_FAT_SUCCESS) { + log_fd = status; + log_open = 1; + } + } +} + +static void +ao_log_close(void) +{ + if (log_open) { + log_open = 0; + ao_fat_close(log_fd); + log_fd = -1; + } +} + +uint8_t +ao_log_full(void) +{ + return ao_fat_full(); +} + +uint8_t +ao_log_mega(struct ao_log_mega *log) +{ + uint8_t wrote = 0; + ao_mutex_get(&log_mutex); + if (log->type == AO_LOG_GPS_TIME) { + if (log_open && + (log_year != log->u.gps.year || + log_month != log->u.gps.month || + log_day != log->u.gps.day)) { + ao_log_close(); + } + if (!log_open) { + log_year = log->u.gps.year; + log_month = log->u.gps.month; + log_day = log->u.gps.day; + ao_log_open(); + } + } + if (log_open) { + wrote = ao_fat_write(log_fd, log, sizeof (*log)) == AO_FAT_SUCCESS; + ao_fat_sync(); + } + ao_mutex_put(&log_mutex); + return wrote; +} + +void +ao_log_flush(void) +{ + ao_fat_sync(); +} diff --git a/src/drivers/ao_rfpa0133.c b/src/drivers/ao_rfpa0133.c new file mode 100644 index 00000000..a98e261a --- /dev/null +++ b/src/drivers/ao_rfpa0133.c @@ -0,0 +1,47 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" + +static void +ao_rfpa0133_set_power(uint8_t power) +{ + ao_gpio_set(AO_PA_GAIN_8_GPIO, AO_PA_GAIN_8_PIN, AO_PA_GAIN_8, power & 1); + ao_gpio_set(AO_PA_GAIN_16_GPIO, AO_PA_GAIN_16_PIN, AO_PA_GAIN_16, (power >> 1) & 1); +} + +void +ao_radio_pa_on(void) +{ + ao_rfpa0133_set_power(ao_config.radio_amp); + ao_gpio_set(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 1); +} + +void +ao_radio_pa_off(void) +{ + ao_gpio_set(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 0); + ao_rfpa0133_set_power(0); +} + +void +ao_radio_pa_init(void) +{ + ao_enable_output(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 0); + ao_enable_output(AO_PA_GAIN_8_GPIO, AO_PA_GAIN_8_PIN, AO_PA_GAIN_8, 0); + ao_enable_output(AO_PA_GAIN_16_GPIO, AO_PA_GAIN_16_PIN, AO_PA_GAIN_16, 0); +} diff --git a/src/drivers/ao_rfpa0133.h b/src/drivers/ao_rfpa0133.h new file mode 100644 index 00000000..2ba7f699 --- /dev/null +++ b/src/drivers/ao_rfpa0133.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_RFPA0133_H_ +#define _AO_RFPA0133_H_ + +void +ao_rfpa0133_on(void); + +void +ao_rfpa0133_off(void); + +void +ao_rfpa0133_init(void); + +#endif /* _AO_RFPA0133_H_ */ diff --git a/src/drivers/ao_sdcard.c b/src/drivers/ao_sdcard.c new file mode 100644 index 00000000..c13017f0 --- /dev/null +++ b/src/drivers/ao_sdcard.c @@ -0,0 +1,700 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" +#include "ao_sdcard.h" + +extern uint8_t ao_radio_mutex; + +#define ao_sdcard_get_slow() do { ao_mutex_get(&ao_radio_mutex); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz); } while (0) +#define ao_sdcard_get() do { ao_mutex_get(&ao_radio_mutex); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST); } while (0) +#define ao_sdcard_put() do { ao_spi_put(AO_SDCARD_SPI_BUS); ao_mutex_put(&ao_radio_mutex); } while (0) +#define ao_sdcard_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_send(d,l) ao_spi_send((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_recv(d,l) ao_spi_recv((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_select() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,0) +#define ao_sdcard_deselect() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1) + +/* Include SD card commands */ +#define SDCARD_DEBUG 0 + +/* Spew SD tracing */ +#define SDCARD_TRACE 0 + +/* Emit error and warning messages */ +#define SDCARD_WARN 0 + +static uint8_t initialized; +static uint8_t present; +static uint8_t mutex; +static enum ao_sdtype sdtype; + +#define ao_sdcard_lock() ao_mutex_get(&mutex) +#define ao_sdcard_unlock() ao_mutex_put(&mutex) + +#if SDCARD_TRACE +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +#if SDCARD_WARN +#define WARN(...) printf(__VA_ARGS__) +#else +#define WARN(...) +#endif + +#define later(x,y) ((int16_t) ((x) - (y)) >= 0) + +/* + * Wait while the card is busy. The card will return a stream of 0xff + * when it is ready to accept a command + */ + +static uint8_t +ao_sdcard_wait_busy(void) +{ + uint16_t timeout = ao_time() + SDCARD_BUSY_TIMEOUT; + uint8_t reply; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG("\t\twait busy %02x\n", reply); + if (reply == 0xff) + break; + if (later(ao_time(), timeout)) { + WARN("wait busy timeout\n"); + return 0; + } + } + return 1; +} + + +/* + * Send an SD command and await the status reply + */ + +static uint8_t +ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg) +{ + uint8_t data[6]; + uint8_t reply; + int i; + uint16_t timeout; + + DBG ("\tsend_cmd %d arg %08x\n", cmd, arg); + + /* Wait for the card to not be busy */ + if (cmd != SDCARD_GO_IDLE_STATE) { + if (!ao_sdcard_wait_busy()) + return SDCARD_STATUS_TIMEOUT; + } + + data[0] = cmd & 0x3f | 0x40; + data[1] = arg >> 24; + data[2] = arg >> 16; + data[3] = arg >> 8; + data[4] = arg; + if (cmd == SDCARD_GO_IDLE_STATE) + data[5] = 0x95; /* Valid for 0 arg */ + else if (cmd == SDCARD_SEND_IF_COND) + data[5] = 0x87; /* Valid for 0x1aa arg */ + else + data[5] = 0xff; /* no CRC */ + ao_sdcard_send(data, 6); + + /* The first reply byte will be the status, + * which must have the high bit clear + */ + timeout = ao_time() + SDCARD_CMD_TIMEOUT; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG ("\t\tgot byte %02x\n", reply); + if ((reply & 0x80) == 0) + break; + if (later(ao_time(), timeout)) { + WARN("send_cmd %02x timeout\n", cmd); + return SDCARD_STATUS_TIMEOUT; + } + } +#if SDCARD_WARN + if (reply != SDCARD_STATUS_READY_STATE && reply != SDCARD_STATUS_IDLE_STATE) + WARN("send_cmd %d failed %02x\n", cmd, reply); +#endif + return reply; +} + +/* + * Retrieve any reply, discarding the trailing CRC byte + */ +static void +ao_sdcard_recv_reply(uint8_t *reply, int len) +{ + uint8_t discard; + + if (len) + ao_sdcard_recv(reply, len); + /* trailing byte */ + ao_sdcard_recv(&discard, 1); +} + +/* + * Switch to 'idle' state. This is used to get the card into SPI mode + */ +static uint8_t +ao_sdcard_go_idle_state(void) +{ + uint8_t ret; + + DBG ("go_idle_state\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_GO_IDLE_STATE, 0); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + DBG ("\tgo_idle_state status %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_send_op_cond(void) +{ + uint8_t ret; + + DBG ("send_op_cond\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SEND_OP_COND, 0); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + DBG ("\tsend_op_cond %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4]) +{ + uint8_t ret; + + DBG ("send_if_cond\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SEND_IF_COND, arg); + if (ret != SDCARD_STATUS_IDLE_STATE) { + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret; + } + ao_sdcard_recv_reply(send_if_cond_response, 4); + DBG ("send_if_cond status %02x response %02x %02x %02x %02x\n", + ret, + send_if_cond_response[0], + send_if_cond_response[1], + send_if_cond_response[2], + send_if_cond_response[3]); + ao_sdcard_deselect(); + return ret; +} + +/* + * _ao_sdcard_send_status + * + * Get the 2-byte status value. + * + * Called from other functions with CS held low already, + * hence prefixing the name with '_' + */ +static uint16_t +_ao_sdcard_send_status(void) +{ + uint8_t ret; + uint8_t extra; + + DBG ("send_status\n"); + ret = ao_sdcard_send_cmd(SDCARD_SEND_STATUS, 0); + ao_sdcard_recv_reply(&extra, 1); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret | (extra << 8); +} + +/* + * ao_sdcard_set_blocklen + * + * Set the block length for future read and write commands + */ +static uint8_t +ao_sdcard_set_blocklen(uint32_t blocklen) +{ + uint8_t ret; + + DBG ("set_blocklen %d\n", blocklen); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret; +} + +/* + * _ao_sdcard_app_cmd + * + * Send the app command prefix + * + * Called with the CS held low, hence + * the '_' prefix + */ +static uint8_t +_ao_sdcard_app_cmd(void) +{ + uint8_t ret; + + DBG ("app_cmd\n"); + ret = ao_sdcard_send_cmd(SDCARD_APP_CMD, 0); + ao_sdcard_recv_reply(NULL, 0); + DBG ("\tapp_cmd status %02x\n"); + return ret; +} + +static uint8_t +ao_sdcard_app_send_op_cond(uint32_t arg) +{ + uint8_t ret; + + DBG("send_op_comd\n"); + ao_sdcard_select(); + ret = _ao_sdcard_app_cmd(); + if (ret != SDCARD_STATUS_IDLE_STATE) + goto bail; + ret = ao_sdcard_send_cmd(SDCARD_APP_SEND_OP_COMD, arg); + ao_sdcard_recv_reply(NULL, 0); +bail: + ao_sdcard_deselect(); + DBG ("\tapp_send_op_cond status %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_read_ocr(uint8_t read_ocr_response[4]) +{ + uint8_t ret; + + DBG ("read_ocr\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_READ_OCR, 0); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tread_ocr failed %02x\n", ret); + else { + ao_sdcard_recv_reply(read_ocr_response, 4); + DBG ("\tread_ocr status %02x response %02x %02x %02x %02x\n", ret, + read_ocr_response[0], read_ocr_response[1], + read_ocr_response[2], read_ocr_response[3]); + } + ao_sdcard_deselect(); + return ret; +} + +/* + * Follow the flow-chart defined by the SD group to + * initialize the card and figure out what kind it is + */ +static void +ao_sdcard_setup(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + DBG ("Testing sdcard\n"); + + ao_sdcard_get_slow(); + /* + * min 74 clocks with CS high + */ + ao_sdcard_send_fixed(0xff, 10); + + /* Reset the card and get it into SPI mode */ + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_RETRY) + goto bail; + + /* Figure out what kind of card we have */ + sdtype = ao_sdtype_unknown; + + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_OP_COND_RETRY; i++) { + ao_delay(AO_MS_TO_TICKS(10)); + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_OP_COND_RETRY; i++) { + ao_delay(AO_MS_TO_TICKS(10)); + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + sdtype = ao_sdtype_mmc3; + } else { + /* SD */ + if (sdver2 != 0) { + ret = ao_sdcard_read_ocr(response); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + if ((response[0] & 0xc0) == 0xc0) + sdtype = ao_sdtype_sd2block; + else + sdtype = ao_sdtype_sd2byte; + } else { + sdtype = ao_sdtype_sd1; + } + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } + + DBG ("SD card detected, type %d\n", sdtype); +bail: + ao_sdcard_put(); +} + +static uint8_t +_ao_sdcard_reset(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_RETRY) { + ret = 0x3f; + goto bail; + } + + /* Follow the setup path to get the card out of idle state and + * up and running again + */ + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } +bail: + return ret; +} + +/* + * The card will send 0xff until it is ready to send + * the data block at which point it will send the START_BLOCK + * marker followed by the data. This function waits while + * the card is sending 0xff + */ +static uint8_t +ao_sdcard_wait_block_start(void) +{ + uint8_t v; + uint16_t timeout = ao_time() + SDCARD_BLOCK_TIMEOUT; + + DBG ("\twait_block_start\n"); + for (;;) { + ao_sdcard_recv(&v, 1); + DBG("\t\trecv %02x\n", v); + if (v != 0xff) + break; + if (later(ao_time(), timeout)) { + printf ("wait block start timeout\n"); + return 0xff; + } + } + return v; +} + +/* + * Read a block of 512 bytes from the card + */ +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data) +{ + uint8_t ret; + uint8_t start_block; + uint8_t crc[2]; + int tries; + + ao_sdcard_lock(); + if (!initialized) { + ao_sdcard_setup(); + initialized = 1; + if (sdtype != ao_sdtype_unknown) + present = 1; + } + if (!present) { + ao_sdcard_unlock(); + return 0; + } + DBG("read block %d\n", block); + if (sdtype != ao_sdtype_sd2block) + block <<= 9; + + ao_sdcard_get(); + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) { + uint16_t status; + WARN ("read block command failed %d status %02x\n", block, ret); + status = _ao_sdcard_send_status(); + WARN ("\tstatus now %04x\n", status); + goto bail; + } + + ao_sdcard_send_fixed(0xff, 1); + + /* Wait for the data start block marker */ + start_block = ao_sdcard_wait_block_start(); + if (start_block != SDCARD_DATA_START_BLOCK) { + WARN ("wait block start failed %02x\n", start_block); + ret = 0x3f; + goto bail; + } + + ao_sdcard_recv(data, 512); + ao_sdcard_recv(crc, 2); + bail: + ao_sdcard_deselect(); + if (ret == SDCARD_STATUS_READY_STATE) + break; + if (ret == SDCARD_STATUS_IDLE_STATE) { + ret = _ao_sdcard_reset(); + if (ret != SDCARD_STATUS_READY_STATE) + break; + } + } + ao_sdcard_put(); + ao_sdcard_unlock(); + +#if SDCARD_WARN + if (ret != SDCARD_STATUS_READY_STATE) + WARN("read failed\n"); + else if (tries) + WARN("took %d tries to read %d\n", tries + 1, block); +#endif + + DBG("read %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + return ret == SDCARD_STATUS_READY_STATE; +} + +/* + * Write a block of 512 bytes to the card + */ +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data) +{ + uint8_t ret; + uint8_t response[1]; + uint8_t start_block[8]; + uint16_t status; + static uint8_t check_data[512]; + int i; + int tries; + + ao_sdcard_lock(); + if (!initialized) { + ao_sdcard_setup(); + initialized = 1; + if (sdtype != ao_sdtype_unknown) + present = 1; + } + if (!present) { + ao_sdcard_unlock(); + return 0; + } + DBG("write block %d\n", block); + if (sdtype != ao_sdtype_sd2block) + block <<= 9; + + ao_sdcard_get(); + + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_WRITE_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + + /* Write a pad byte followed by the data start block marker */ + start_block[0] = 0xff; + start_block[1] = SDCARD_DATA_START_BLOCK; + ao_sdcard_send(start_block, 2); + + /* Send the data */ + ao_sdcard_send(data, 512); + + /* Fake the CRC */ + ao_sdcard_send_fixed(0xff, 2); + + /* See if the card liked the data */ + ao_sdcard_recv(response, sizeof (response)); + if ((response[0] & SDCARD_DATA_RES_MASK) != SDCARD_DATA_RES_ACCEPTED) { + int i; + WARN("Data not accepted, response"); + for (i = 0; i < sizeof (response); i++) + WARN(" %02x", response[i]); + WARN("\n"); + ret = 0x3f; + goto bail; + } + + /* Wait for the bus to go idle (should be done with an interrupt?) */ + if (!ao_sdcard_wait_busy()) { + ret = 0x3f; + goto bail; + } + + /* Check the current status after the write completes */ + status = _ao_sdcard_send_status(); + if ((status & 0xff) != SDCARD_STATUS_READY_STATE) { + WARN ("send status after write %04x\n", status); + ret = status & 0xff; + goto bail; + } + bail: + ao_sdcard_deselect(); + DBG("write %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + if (ret == SDCARD_STATUS_READY_STATE) + break; + } + ao_sdcard_put(); + ao_sdcard_unlock(); + if (tries) + WARN("took %d tries to write %d\n", tries + 1, block); + + return ret == SDCARD_STATUS_READY_STATE; +} + +#if SDCARD_DEBUG +static uint8_t test_data[512]; + +static void +ao_sdcard_test_read(void) +{ + int i; + + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + + for (i = 0; i < 100; i++) { + printf ("."); flush(); + if (!ao_sdcard_read_block(ao_cmd_lex_u32+i, test_data)) { + printf ("read error %d\n", i); + return; + } + } + printf ("data:"); + for (i = 0; i < 18; i++) + printf (" %02x", test_data[i]); + printf ("\n"); +} + +static void +ao_sdcard_test_write(void) +{ + int i; + printf ("data:"); + for (i = 0; i < 16; i++) { + test_data[i]++; + printf (" %02x", test_data[i]); + } + printf ("\n"); + if (!ao_sdcard_write_block(1, test_data)) { + printf ("write error\n"); + return; + } +} + +static const struct ao_cmds ao_sdcard_cmds[] = { + { ao_sdcard_test_read, "x\0Test read" }, + { ao_sdcard_test_write, "y\0Test read" }, + { 0, NULL }, +}; +#endif + +void +ao_sdcard_init(void) +{ + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_SCK_PIN, STM_PUPDR_PULL_UP); + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MISO_PIN, STM_PUPDR_PULL_UP); + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MOSI_PIN, STM_PUPDR_PULL_UP); + ao_spi_init_cs(AO_SDCARD_SPI_CS_PORT, (1 << AO_SDCARD_SPI_CS_PIN)); +#if SDCARD_DEBUG + ao_cmd_register(&ao_sdcard_cmds[0]); +#endif +} diff --git a/src/drivers/ao_sdcard.h b/src/drivers/ao_sdcard.h new file mode 100644 index 00000000..50b70c73 --- /dev/null +++ b/src/drivers/ao_sdcard.h @@ -0,0 +1,82 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_SDCARD_H_ +#define _AO_SDCARD_H_ + +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data); + +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data); + +void +ao_sdcard_init(void); + +/* Commands */ +#define SDCARD_GO_IDLE_STATE 0 +#define SDCARD_SEND_OP_COND 1 +#define SDCARD_SEND_IF_COND 8 +#define SDCARD_SEND_CSD 9 +#define SDCARD_SEND_CID 10 +#define SDCARD_SEND_STATUS 13 +#define SDCARD_SET_BLOCKLEN 16 +#define SDCARD_READ_BLOCK 17 +#define SDCARD_WRITE_BLOCK 24 +#define SDCARD_WRITE_MULTIPLE_BLOCK 25 +#define SDCARD_ERASE_WR_BLK_START 32 +#define SDCARD_ERASE_WR_BLK_END 33 +#define SDCARD_ERASE 38 +#define SDCARD_APP_CMD 55 +#define SDCARD_READ_OCR 58 + +/* App commands */ +#define SDCARD_APP_SET_WR_BLK_ERASE_COUNT 23 +#define SDCARD_APP_SEND_OP_COMD 41 + +/* Status */ +#define SDCARD_STATUS_READY_STATE 0x00 +#define SDCARD_STATUS_IDLE_STATE 0x01 +#define SDCARD_STATUS_ERASE_RESET 0x02 +#define SDCARD_STATUS_ILLEGAL_COMMAND 0x04 +#define SDCARD_STATUS_COM_CRC_ERROR 0x08 +#define SDCARD_STATUS_ERASE_SEQ_ERROR 0x10 +#define SDCARD_STATUS_ADDRESS_ERROR 0x20 +#define SDCARD_STATUS_PARAMETER_ERROR 0x40 +#define SDCARD_STATUS_TIMEOUT 0xff + +#define SDCARD_DATA_START_BLOCK 0xfe +#define SDCARD_STOP_TRAN_TOKEN 0xfd +#define SDCARD_WRITE_MULTIPLE_TOKEN 0xfc +#define SDCARD_DATA_RES_MASK 0x1f +#define SDCARD_DATA_RES_ACCEPTED 0x05 + +#define SDCARD_CMD_TIMEOUT AO_MS_TO_TICKS(20) +#define SDCARD_BUSY_TIMEOUT AO_MS_TO_TICKS(20) +#define SDCARD_BLOCK_TIMEOUT AO_MS_TO_TICKS(200) +#define SDCARD_IDLE_RETRY 10 +#define SDCARD_OP_COND_RETRY 10 + +enum ao_sdtype { + ao_sdtype_unknown, + ao_sdtype_mmc3, + ao_sdtype_sd1, + ao_sdtype_sd2byte, + ao_sdtype_sd2block, +}; + +#endif /* _AO_SDCARD_H_ */ diff --git a/src/megametrum-v0.1/.gitignore b/src/megametrum-v0.1/.gitignore deleted file mode 100644 index b04d3950..00000000 --- a/src/megametrum-v0.1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -ao_product.h -megametrum-*.elf diff --git a/src/stm-bringup/Makefile b/src/stm-bringup/Makefile index 5cc94bd9..1bc5aaad 100644 --- a/src/stm-bringup/Makefile +++ b/src/stm-bringup/Makefile @@ -8,8 +8,8 @@ endif CC=arm-none-eabi-gcc OBJCOPY=arm-none-eabi-objcopy -PDCLIB=/home/keithp/sat -C_LIB=$(PDCLIB)/lib/pdclib.a +PDCLIB=/opt/cortex +C_LIB=$(PDCLIB)/lib/pdclib-cortex-m3.a C_INC=-I$(PDCLIB)/include DEF_CFLAGS=-g -std=gnu99 -Os -mlittle-endian -mthumb -ffreestanding -nostdlib -I. -I../../src/stm $(C_INC) diff --git a/src/stm/Makefile.defs b/src/stm/Makefile.defs index 3ce6eead..c8bb7d70 100644 --- a/src/stm/Makefile.defs +++ b/src/stm/Makefile.defs @@ -13,8 +13,8 @@ vpath ao-make-product.5c ../util objcopy -O ihex $*.elf $@ CC=arm-none-eabi-gcc -SAT=/home/keithp/sat -SAT_CLIB=$(SAT)/lib/pdclib.a +SAT=/opt/cortex +SAT_CLIB=$(SAT)/lib/pdclib-cortex-m3.a SAT_CFLAGS=-I$(SAT)/include ifndef VERSION diff --git a/src/stm/ao_eeprom_stm.c b/src/stm/ao_eeprom_stm.c index 5a75a97d..58783f1a 100644 --- a/src/stm/ao_eeprom_stm.c +++ b/src/stm/ao_eeprom_stm.c @@ -55,10 +55,16 @@ ao_storage_erase(ao_pos_t pos) __reentrant static void ao_intflash_unlock(void) { + /* Disable backup write protection */ + stm_pwr.cr |= (1 << STM_PWR_CR_DBP); + /* Unlock Data EEPROM and FLASH_PECR register */ stm_flash.pekeyr = STM_FLASH_PEKEYR_PEKEY1; stm_flash.pekeyr = STM_FLASH_PEKEYR_PEKEY2; + if (stm_flash.pecr & (1 << STM_FLASH_PECR_PELOCK)) + printf ("eeprom unlock failed\n"); + /* Configure the FTDW bit (FLASH_PECR[8]) to execute * word write, whatever the previous value of the word * being written to @@ -68,8 +74,8 @@ ao_intflash_unlock(void) (0 << STM_FLASH_PECR_EOPIE) | (0 << STM_FLASH_PECR_FPRG) | (0 << STM_FLASH_PECR_ERASE) | - (0 << STM_FLASH_PECR_FTDW) | - (1 << STM_FLASH_PECR_DATA) | + (1 << STM_FLASH_PECR_FTDW) | + (0 << STM_FLASH_PECR_DATA) | (0 << STM_FLASH_PECR_PROG) | (0 << STM_FLASH_PECR_OPTLOCK) | (0 << STM_FLASH_PECR_PRGLOCK) | @@ -97,15 +103,9 @@ ao_intflash_write32(uint16_t pos, uint32_t w) addr = (uint32_t *) (stm_eeprom + pos); - /* Erase previous word */ - *addr = 0; + /* Write a word to a valid address in the data EEPROM */ + *addr = w; ao_intflash_wait(); - - if (w) { - /* Write a word to a valid address in the data EEPROM */ - *addr = w; - ao_intflash_wait(); - } } static void diff --git a/src/stm/ao_i2c_stm.c b/src/stm/ao_i2c_stm.c index 779e2275..809b5c6f 100644 --- a/src/stm/ao_i2c_stm.c +++ b/src/stm/ao_i2c_stm.c @@ -36,7 +36,7 @@ static uint16_t ao_i2c_addr[STM_NUM_I2C]; uint8_t ao_i2c_mutex[STM_NUM_I2C]; # define I2C_HIGH_SLOW 5000 /* ns, 100kHz clock */ -#ifdef MEGAMETRUM +#ifdef TELEMEGA # define I2C_HIGH_FAST 2000 /* ns, 167kHz clock */ #else # define I2C_HIGH_FAST 1000 /* ns, 333kHz clock */ diff --git a/src/stm/ao_spi_stm.c b/src/stm/ao_spi_stm.c index 7b4af964..56329c24 100644 --- a/src/stm/ao_spi_stm.c +++ b/src/stm/ao_spi_stm.c @@ -160,6 +160,8 @@ ao_spi_recv(void *block, uint16_t len, uint8_t spi_index) uint8_t mosi_dma_index = ao_spi_stm_info[AO_SPI_INDEX(spi_index)].mosi_dma_index; uint8_t miso_dma_index = ao_spi_stm_info[AO_SPI_INDEX(spi_index)].miso_dma_index; + spi_dev_null = 0xff; + /* Set up transmit DMA to make the SPI hardware actually run */ ao_dma_set_transfer(mosi_dma_index, &stm_spi->dr, diff --git a/src/stm/ao_timer.c b/src/stm/ao_timer.c index e07625d8..8b7c2327 100644 --- a/src/stm/ao_timer.c +++ b/src/stm/ao_timer.c @@ -18,15 +18,12 @@ #include "ao.h" #include <ao_task.h> -volatile __data AO_TICK_TYPE ao_tick_count; +volatile AO_TICK_TYPE ao_tick_count; -uint16_t ao_time(void) +AO_TICK_TYPE +ao_time(void) { - uint16_t v; - ao_arch_critical( - v = ao_tick_count; - ); - return v; + return ao_tick_count; } #if AO_DATA_ALL diff --git a/src/telebt-v0.0/.sdcdbrc b/src/telebt-v0.0/.sdcdbrc deleted file mode 100644 index 710b4a2f..00000000 --- a/src/telebt-v0.0/.sdcdbrc +++ /dev/null @@ -1 +0,0 @@ ---directory=.. diff --git a/src/telebt-v0.0/Makefile b/src/telebt-v0.0/Makefile deleted file mode 100644 index e89639ab..00000000 --- a/src/telebt-v0.0/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# -# TeleBT v0.0 build -# - -TELEBT_VER=0.0 -TELEBT_DEF=0_0 - -include ../product/Makefile.telebt - diff --git a/src/telebt-v0.1/.gitignore b/src/telebt-v0.1/.gitignore deleted file mode 100644 index 1acfbfcc..00000000 --- a/src/telebt-v0.1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -telebt-* -ao_product.h diff --git a/src/telebt-v0.1/Makefile b/src/telebt-v0.1/Makefile deleted file mode 100644 index 90cd3cac..00000000 --- a/src/telebt-v0.1/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# -# TeleBT v0.1 build -# - -TELEBT_VER=0.1 -TELEBT_DEF=0_1 - -TELEBT_INC = \ - ao_25lc1024.h - -TELEBT_SRC = \ - ao_beep.c \ - ao_log_single.c \ - ao_log_telem.c \ - ao_report.c \ - ao_spi.c \ - ao_storage.c \ - ao_m25.c - -include ../product/Makefile.telebt - diff --git a/src/telebt-v0.0/.gitignore b/src/telebt-v1.0/.gitignore index 1acfbfcc..1acfbfcc 100644 --- a/src/telebt-v0.0/.gitignore +++ b/src/telebt-v1.0/.gitignore diff --git a/src/telebt-v0.1/.sdcdbrc b/src/telebt-v1.0/.sdcdbrc index b9f6129c..b9f6129c 100644 --- a/src/telebt-v0.1/.sdcdbrc +++ b/src/telebt-v1.0/.sdcdbrc diff --git a/src/product/Makefile.telebt b/src/telebt-v1.0/Makefile index fd52cec4..911a8b09 100644 --- a/src/product/Makefile.telebt +++ b/src/telebt-v1.0/Makefile @@ -1,8 +1,9 @@ # # TeleBT build file # -# Define TELEBT_VER, TELEBT_DEF, TELEBT_INC and TELEBT_SRC -# and include this file + +TELEBT_VER=1.0 +TELEBT_DEF=1_0 vpath %.c ..:../core:../cc1111:../drivers:../product vpath %.h ..:../core:../cc1111:../drivers:../product @@ -18,8 +19,7 @@ INC = \ ao_arch.h \ ao_arch_funcs.h \ cc1111.h \ - ao_product.h \ - $(TELEBT_INC) + ao_product.h CORE_SRC = \ ao_cmd.c \ @@ -35,6 +35,7 @@ CORE_SRC = \ CC1111_SRC = \ ao_dbg.c \ + ao_adc.c \ ao_dma.c \ ao_led.c \ ao_packet.c \ @@ -57,8 +58,7 @@ SRC = \ $(CORE_SRC) \ $(CC1111_SRC) \ $(DRIVER_SRC) \ - $(PRODUCT_SRC) \ - $(TELEBT_SRC) + $(PRODUCT_SRC) PROGNAME = telebt-v$(TELEBT_VER) PROG = $(PROGNAME)-$(VERSION).ihx diff --git a/src/telebt-v1.0/ao_pins.h b/src/telebt-v1.0/ao_pins.h new file mode 100644 index 00000000..9e47f3b8 --- /dev/null +++ b/src/telebt-v1.0/ao_pins.h @@ -0,0 +1,101 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_PINS_H_ +#define _AO_PINS_H_ + +#define HAS_RADIO 1 +#define HAS_FLIGHT 0 +#define HAS_USB 1 +#define HAS_BEEP 0 +#define HAS_SERIAL_1 1 +#define HAS_SERIAL_1_ALT_1 1 +#define HAS_SERIAL_1_ALT_2 0 +#define HAS_SERIAL_1_HW_FLOW 1 +#define USE_SERIAL_1_STDIN 1 +#define DELAY_SERIAL_1_STDIN 1 +#define HAS_DBG 1 +#define HAS_EEPROM 0 +#define HAS_LOG 0 +#define USE_INTERNAL_FLASH 0 +#define HAS_BTM 1 +#define DBG_ON_P1 1 +#define DBG_ON_P0 0 +#define PACKET_HAS_MASTER 1 +#define PACKET_HAS_SLAVE 0 +#define AO_LED_RED 1 +#define AO_LED_BLUE 2 +#define LEDS_AVAILABLE (AO_LED_RED|AO_LED_BLUE) +#define AO_MONITOR_LED AO_LED_RED +#define AO_BT_LED AO_LED_BLUE +#define BT_LINK_ON_P2 0 +#define BT_LINK_ON_P1 1 +#define BT_LINK_PIN_INDEX 7 +#define BT_LINK_PIN P1_7 +#define HAS_MONITOR 1 +#define LEGACY_MONITOR 0 + +#define HAS_ADC 1 +#define AO_PAD_ADC_BATT 0 +#define AO_ADC_PINS (1 << AO_PAD_ADC_BATT) + +struct ao_adc { + int16_t batt; +}; + +#define AO_ADC_DUMP(p) \ + printf ("tick: %5u batt %5d\n", \ + (p)->tick, \ + (p)->adc.batt) + +#if DBG_ON_P1 + + #define DBG_CLOCK (1 << 4) /* mi0 */ + #define DBG_DATA (1 << 5) /* mo0 */ + #define DBG_RESET_N (1 << 3) /* c0 */ + + #define DBG_CLOCK_PIN (P1_4) + #define DBG_DATA_PIN (P1_5) + #define DBG_RESET_N_PIN (P1_3) + + #define DBG_PORT_NUM 1 + #define DBG_PORT P1 + #define DBG_PORT_SEL P1SEL + #define DBG_PORT_INP P1INP + #define DBG_PORT_DIR P1DIR + +#endif /* DBG_ON_P1 */ + +#if DBG_ON_P0 + + #define DBG_CLOCK (1 << 3) + #define DBG_DATA (1 << 4) + #define DBG_RESET_N (1 << 5) + + #define DBG_CLOCK_PIN (P0_3) + #define DBG_DATA_PIN (P0_4) + #define DBG_RESET_N_PIN (P0_5) + + #define DBG_PORT_NUM 0 + #define DBG_PORT P0 + #define DBG_PORT_SEL P0SEL + #define DBG_PORT_INP P0INP + #define DBG_PORT_DIR P0DIR + +#endif /* DBG_ON_P0 */ + +#endif /* _AO_PINS_H_ */ diff --git a/src/product/ao_telebt.c b/src/telebt-v1.0/ao_telebt.c index b147eabc..935cde7d 100644 --- a/src/product/ao_telebt.c +++ b/src/telebt-v1.0/ao_telebt.c @@ -17,10 +17,6 @@ #include "ao.h" -#if HAS_LOG -__code uint8_t ao_log_format = AO_LOG_FORMAT_NONE; /* until we actually log stuff */ -#endif - void main(void) { @@ -33,32 +29,16 @@ main(void) ao_task_init(); ao_timer_init(); -#if HAS_BEEP - ao_beep_init(); -#endif ao_cmd_init(); -#if HAS_EEPROM - ao_spi_init(); - ao_storage_init(); -#endif ao_usb_init(); ao_monitor_init(); -#if HAS_LOG - ao_report_init(); -#endif ao_radio_init(); ao_packet_master_init(); + ao_adc_init(); ao_btm_init(); -#if HAS_LOG - ao_log_single_init(); -#endif #if HAS_DBG ao_dbg_init(); #endif -#if HAS_AES - ao_aes_init(); - ao_radio_cmac_init(); -#endif ao_config_init(); ao_start_scheduler(); } diff --git a/src/telegps-v0.1/.gitignore b/src/telegps-v0.1/.gitignore new file mode 100644 index 00000000..b1e261f5 --- /dev/null +++ b/src/telegps-v0.1/.gitignore @@ -0,0 +1,2 @@ +ao_product.h +telegps-*.elf diff --git a/src/telegps-v0.1/Makefile b/src/telegps-v0.1/Makefile new file mode 100644 index 00000000..aae37660 --- /dev/null +++ b/src/telegps-v0.1/Makefile @@ -0,0 +1,98 @@ +# +# AltOS build +# +# + +include ../stm/Makefile.defs + +INC = \ + ao.h \ + ao_arch.h \ + ao_arch_funcs.h \ + ao_pins.h \ + ao_product.h \ + ao_task.h \ + ao_whiten.h \ + ao_cc115l.h \ + ao_fec.h \ + stm32l.h \ + ao_sdcard.h \ + ao_bufio.h \ + ao_fat.h \ + Makefile + + +#PROFILE=ao_profile.c +#PROFILE_DEF=-DAO_PROFILE=1 + +#SAMPLE_PROFILE=ao_sample_profile.c \ +# ao_sample_profile_timer.c +#SAMPLE_PROFILE_DEF=-DHAS_SAMPLE_PROFILE=1 + +#STACK_GUARD=ao_mpu_stm.c +#STACK_GUARD_DEF=-DHAS_STACK_GUARD=1 + +ALTOS_SRC = \ + ao_interrupt.c \ + ao_product.c \ + ao_romconfig.c \ + ao_cmd.c \ + ao_config.c \ + ao_task.c \ + ao_led.c \ + ao_stdio.c \ + ao_panic.c \ + ao_timer.c \ + ao_mutex.c \ + ao_freq.c \ + ao_dma_stm.c \ + ao_spi_stm.c \ + ao_usb_stm.c \ + ao_exti_stm.c \ + ao_serial_stm.c \ + ao_gps_skytraq.c \ + ao_cc115l.c \ + ao_fec_tx.c \ + ao_rfpa0133.c \ + ao_aprs.c \ + ao_storage.c \ + ao_eeprom_stm.c \ + ao_sdcard.c \ + ao_bufio.c \ + ao_fat.c \ + ao_log_fat.c \ + ao_gps_report_mega.c \ + ao_telemetry.c \ + $(SAMPLE_PROFILE) + +PRODUCT=TeleGPS-v0.1 +PRODUCT_DEF=-DTELEGPS +IDPRODUCT=0x0025 + +CFLAGS = $(PRODUCT_DEF) $(STM_CFLAGS) $(PROFILE_DEF) $(SAMPLE_PROFILE_DEF) $(STACK_GUARD_DEF) -Os -g + +PROGNAME=telegps-v0.1 +PROG=$(PROGNAME)-$(VERSION).elf + +SRC=$(ALTOS_SRC) ao_telegps.c +OBJ=$(SRC:.c=.o) + +all: $(PROG) + +$(PROG): Makefile $(OBJ) altos.ld + $(call quiet,CC) $(LDFLAGS) $(CFLAGS) -o $(PROG) $(OBJ) $(SAT_CLIB) -lgcc + +$(OBJ): $(INC) + +ao_product.h: ao-make-product.5c ../Version + $(call quiet,NICKLE,$<) $< -m altusmetrum.org -i $(IDPRODUCT) -p $(PRODUCT) -v $(VERSION) > $@ + +distclean: clean + +clean: + rm -f *.o $(PROGNAME)-*.elf + rm -f ao_product.h + +install: + +uninstall: diff --git a/src/telegps-v0.1/ao_pins.h b/src/telegps-v0.1/ao_pins.h new file mode 100644 index 00000000..5bea2681 --- /dev/null +++ b/src/telegps-v0.1/ao_pins.h @@ -0,0 +1,182 @@ +/* + * Copyright © 2012 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_PINS_H_ +#define _AO_PINS_H_ + +#define HAS_TASK_QUEUE 1 + +/* 8MHz High speed external crystal */ +#define AO_HSE 8000000 + +/* PLLVCO = 96MHz (so that USB will work) */ +#define AO_PLLMUL 12 +#define AO_RCC_CFGR_PLLMUL (STM_RCC_CFGR_PLLMUL_12) + +/* SYSCLK = 32MHz (no need to go faster than CPU) */ +#define AO_PLLDIV 3 +#define AO_RCC_CFGR_PLLDIV (STM_RCC_CFGR_PLLDIV_3) + +/* HCLK = 32MHz (CPU clock) */ +#define AO_AHB_PRESCALER 1 +#define AO_RCC_CFGR_HPRE_DIV STM_RCC_CFGR_HPRE_DIV_1 + +/* Run APB1 at 16MHz (HCLK/2) */ +#define AO_APB1_PRESCALER 2 +#define AO_RCC_CFGR_PPRE1_DIV STM_RCC_CFGR_PPRE2_DIV_2 + +/* Run APB2 at 16MHz (HCLK/2) */ +#define AO_APB2_PRESCALER 2 +#define AO_RCC_CFGR_PPRE2_DIV STM_RCC_CFGR_PPRE2_DIV_2 + +#define HAS_SERIAL_1 1 +#define USE_SERIAL_1_STDIN 0 +#define SERIAL_1_PB6_PB7 1 +#define SERIAL_1_PA9_PA10 0 + +#define HAS_SERIAL_2 1 +#define USE_SERIAL_2_STDIN 0 +#define SERIAL_2_PA2_PA3 1 +#define SERIAL_2_PD5_PD6 0 + +#define HAS_SERIAL_3 0 +#define USE_SERIAL_3_STDIN 0 +#define SERIAL_3_PB10_PB11 0 +#define SERIAL_3_PC10_PC11 1 +#define SERIAL_3_PD8_PD9 0 + +#define ao_gps_getchar ao_serial2_getchar +#define ao_gps_putchar ao_serial2_putchar +#define ao_gps_set_speed ao_serial2_set_speed +#define ao_gps_fifo (ao_stm_usart2.rx_fifo) + +#define HAS_EEPROM 1 +#define USE_INTERNAL_FLASH 1 +#define HAS_USB 1 +#define HAS_BEEP 0 +#define HAS_RADIO 1 +#define HAS_TELEMETRY 1 +#define HAS_APRS 1 +#define HAS_RADIO_RECV 0 + +#define HAS_SPI_1 1 +#define SPI_1_PA5_PA6_PA7 1 /* SD card */ +#define SPI_1_PB3_PB4_PB5 0 +#define SPI_1_PE13_PE14_PE15 0 +#define SPI_1_OSPEEDR STM_OSPEEDR_10MHz + +#define SPI_1_PORT (&stm_gpioa) +#define SPI_1_SCK_PIN 5 +#define SPI_1_MISO_PIN 6 +#define SPI_1_MOSI_PIN 7 + +#define HAS_SPI_2 1 +#define SPI_2_PB13_PB14_PB15 1 /* CC115L */ +#define SPI_2_PD1_PD3_PD4 0 +#define SPI_2_OSPEEDR STM_OSPEEDR_10MHz + +#define SPI_2_PORT (&stm_gpiob) +#define SPI_2_SCK_PIN 13 +#define SPI_2_MISO_PIN 14 +#define SPI_2_MOSI_PIN 15 + +#define HAS_I2C_1 0 +#define I2C_1_PB8_PB9 1 + +#define HAS_I2C_2 0 +#define I2C_2_PB10_PB11 1 + +#define PACKET_HAS_SLAVE 0 +#define PACKET_HAS_MASTER 0 + +#define LOW_LEVEL_DEBUG 0 + +#define LED_PORT_0_ENABLE STM_RCC_AHBENR_GPIOAEN +#define LED_PORT_0 (&stm_gpioa) +#define LED_PORT_0_MASK (0xff) +#define LED_PORT_0_SHIFT 0 +#define LED_PIN_RED 0 +#define LED_PIN_GREEN 2 +#define AO_LED_RED (1 << LED_PIN_RED) +#define AO_LED_GREEN (1 << LED_PIN_GREEN) + +#define LEDS_AVAILABLE (AO_LED_RED | AO_LED_GREEN) + +#define HAS_GPS 1 +#define HAS_FLIGHT 0 +#define HAS_ADC 0 +#define HAS_LOG 0 + +/* + * Telemetry monitoring + */ +#define HAS_MONITOR 0 +#define LEGACY_MONITOR 0 +#define HAS_MONITOR_PUT 0 +#define AO_MONITOR_LED AO_LED_GREEN + +/* + * Radio (cc115l) + */ + +/* gets pretty close to 434.550 */ + +#define AO_RADIO_CAL_DEFAULT 0x10b6a5 + +#define HAS_RADIO_POWER 1 + +#define AO_FEC_DEBUG 0 +#define AO_CC115L_SPI_CS_PORT (&stm_gpiob) +#define AO_CC115L_SPI_CS_PIN 12 +#define AO_CC115L_SPI_BUS AO_SPI_2_PB13_PB14_PB15 +#define AO_CC115L_SPI stm_spi2 + +#define AO_CC115L_FIFO_INT_GPIO_IOCFG CC115L_IOCFG2 +#define AO_CC115L_FIFO_INT_PORT (&stm_gpioa) +#define AO_CC115L_FIFO_INT_PIN (9) + +#define AO_CC115L_DONE_INT_GPIO_IOCFG CC115L_IOCFG0 +#define AO_CC115L_DONE_INT_PORT (&stm_gpioa) +#define AO_CC115L_DONE_INT_PIN (10) + +#define HAS_RADIO_AMP 1 + +/* + * Power amplifier (RFPA0133) + */ + +#define AO_PA_POWER_GPIO (&stm_gpiob) +#define AO_PA_POWER_PIN 1 +#define AO_PA_GAIN_8_GPIO (&stm_gpiob) +#define AO_PA_GAIN_8_PIN 10 +#define AO_PA_GAIN_16_GPIO (&stm_gpiob) +#define AO_PA_GAIN_16_PIN 11 + +/* + * SD card + */ + +#define AO_SDCARD_SPI_BUS AO_SPI_1_PA5_PA6_PA7 +#define AO_SDCARD_SPI_PORT SPI_1_PORT +#define AO_SDCARD_SPI_SCK_PIN SPI_1_SCK_PIN +#define AO_SDCARD_SPI_MISO_PIN SPI_1_MISO_PIN +#define AO_SDCARD_SPI_MOSI_PIN SPI_1_MOSI_PIN +#define AO_SDCARD_SPI_CS_PORT (&stm_gpioa) +#define AO_SDCARD_SPI_CS_PIN 4 +#define AO_SDCARD_SPI stm_spi1 + +#endif /* _AO_PINS_H_ */ diff --git a/src/telegps-v0.1/ao_telegps.c b/src/telegps-v0.1/ao_telegps.c new file mode 100644 index 00000000..68116bfb --- /dev/null +++ b/src/telegps-v0.1/ao_telegps.c @@ -0,0 +1,68 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <ao.h> +#include <ao_exti.h> +#include <ao_fat.h> + +uint16_t ao_flight_number = 1; + +int +main(void) +{ + ao_clock_init(); + +#if HAS_STACK_GUARD + ao_mpu_init(); +#endif + + ao_task_init(); +// ao_led_init(LEDS_AVAILABLE); +// ao_led_on(AO_LED_RED); + ao_timer_init(); + + + ao_spi_init(); + ao_dma_init(); + ao_exti_init(); + + ao_storage_init(); + + ao_serial_init(); + + ao_cmd_init(); + + ao_usb_init(); + ao_radio_init(); + + ao_fat_init(); + + ao_gps_init(); + ao_gps_report_mega_init(); + + ao_telemetry_init(); + ao_telemetry_set_interval(AO_SEC_TO_TICKS(1)); + ao_rdf_set(1); + +#if HAS_SAMPLE_PROFILE + ao_sample_profile_init(); +#endif + ao_config_init(); + + ao_start_scheduler(); + return 0; +} diff --git a/src/telelco-v0.1/Makefile b/src/telelco-v0.1/Makefile index d2702dd6..a4a83d02 100644 --- a/src/telelco-v0.1/Makefile +++ b/src/telelco-v0.1/Makefile @@ -61,7 +61,7 @@ ALTOS_SRC = \ ao_radio_cmac_cmd.c PRODUCT=TeleLCO-v0.1 -PRODUCT_DEF=-DMEGAMETRUM +PRODUCT_DEF=-DTELEMEGA IDPRODUCT=0x0023 CFLAGS = $(PRODUCT_DEF) $(STM_CFLAGS) $(PROFILE_DEF) -Os -g diff --git a/src/telemega-v0.1/.gitignore b/src/telemega-v0.1/.gitignore new file mode 100644 index 00000000..e67759a2 --- /dev/null +++ b/src/telemega-v0.1/.gitignore @@ -0,0 +1,2 @@ +ao_product.h +telemega-*.elf diff --git a/src/megametrum-v0.1/Makefile b/src/telemega-v0.1/Makefile index a5fdcbb2..16393ea0 100644 --- a/src/megametrum-v0.1/Makefile +++ b/src/telemega-v0.1/Makefile @@ -95,16 +95,16 @@ ALTOS_SRC = \ $(SAMPLE_PROFILE) \ $(STACK_GUARD) -PRODUCT=MegaMetrum-v0.1 -PRODUCT_DEF=-DMEGAMETRUM +PRODUCT=TeleMega-v0.1 +PRODUCT_DEF=-DTELEMEGA IDPRODUCT=0x0023 CFLAGS = $(PRODUCT_DEF) $(STM_CFLAGS) $(PROFILE_DEF) $(SAMPLE_PROFILE_DEF) $(STACK_GUARD_DEF) -Os -g -PROGNAME=megametrum-v0.1 +PROGNAME=telemega-v0.1 PROG=$(PROGNAME)-$(VERSION).elf -SRC=$(ALTOS_SRC) ao_megametrum.c +SRC=$(ALTOS_SRC) ao_telemega.c OBJ=$(SRC:.c=.o) all: $(PROG) diff --git a/src/megametrum-v0.1/ao_pins.h b/src/telemega-v0.1/ao_pins.h index 4c645871..4c645871 100644 --- a/src/megametrum-v0.1/ao_pins.h +++ b/src/telemega-v0.1/ao_pins.h diff --git a/src/megametrum-v0.1/ao_megametrum.c b/src/telemega-v0.1/ao_telemega.c index fbdab64a..fbdab64a 100644 --- a/src/megametrum-v0.1/ao_megametrum.c +++ b/src/telemega-v0.1/ao_telemega.c diff --git a/src/megametrum-v0.1/stlink-pins b/src/telemega-v0.1/stlink-pins index 3bd16d0b..390f8e5d 100644 --- a/src/megametrum-v0.1/stlink-pins +++ b/src/telemega-v0.1/stlink-pins @@ -42,6 +42,14 @@ NRST 2 5 SWDIO 3 4 SWCLK 4 2 +Altus Metrum standard 4-pin connector to MegaMetrum v0.1 misc connector: + + AMstd MM v0.1 +gnd 1 1 +nrst 2 2 +swdio 3 8 +swclk 4 9 + MegaAccel: Jumpers diff --git a/src/teleshield-v0.1/ao_pins.h b/src/teleshield-v0.1/ao_pins.h index 701e25fc..30239afc 100644 --- a/src/teleshield-v0.1/ao_pins.h +++ b/src/teleshield-v0.1/ao_pins.h @@ -46,6 +46,7 @@ #define AO_LED_RED 1 #define AO_LED_GREEN 2 #define AO_MONITOR_LED AO_LED_RED + #define AO_BT_LED AO_LED_GREEN #define LEDS_AVAILABLE (AO_LED_RED|AO_LED_GREEN) #define SPI_CS_ON_P1 1 #define SPI_CS_ON_P0 0 @@ -60,6 +61,7 @@ #define LEGACY_MONITOR 0 #define HAS_RSSI 0 #define HAS_AES 0 + #define HAS_RADIO 1 #endif #if DBG_ON_P1 diff --git a/src/test/.gitignore b/src/test/.gitignore index 8d79d168..8f74c348 100644 --- a/src/test/.gitignore +++ b/src/test/.gitignore @@ -7,6 +7,7 @@ ao_gps_test ao_gps_test_skytraq ao_convert_test ao_convert_pa_test +ao_fat_test ao_fec_test ao_flight_test_mm ao_flight_test_noisy_accel diff --git a/src/test/Makefile b/src/test/Makefile index fccc7937..a62b59c5 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -2,7 +2,7 @@ vpath % ..:../core:../drivers:../util:../micropeak PROGS=ao_flight_test ao_flight_test_baro ao_flight_test_accel ao_flight_test_noisy_accel ao_flight_test_mm \ ao_gps_test ao_gps_test_skytraq ao_convert_test ao_convert_pa_test ao_fec_test \ - ao_aprs_test ao_micropeak_test + ao_aprs_test ao_micropeak_test ao_fat_test INCS=ao_kalman.h ao_ms5607.h ao_log.h ao_data.h altitude-pa.h altitude.h @@ -30,7 +30,7 @@ ao_flight_test_accel: ao_flight_test.c ao_host.h ao_flight.c ao_sample.c ao_kal cc $(CFLAGS) -o $@ -DFORCE_ACCEL=1 ao_flight_test.c ao_flight_test_mm: ao_flight_test.c ao_host.h ao_flight.c ao_sample.c ao_kalman.c $(INCS) - cc -DMEGAMETRUM=1 $(CFLAGS) -o $@ $< -lm + cc -DTELEMEGA=1 $(CFLAGS) -o $@ $< -lm ao_gps_test: ao_gps_test.c ao_gps_sirf.c ao_gps_print.c ao_host.h cc $(CFLAGS) -o $@ $< @@ -64,3 +64,6 @@ check: ao_fec_test ao_flight_test ao_flight_test_baro run-tests ao_micropeak_test: ao_micropeak_test.c ao_microflight.c ao_kalman.h cc $(CFLAGS) -o $@ ao_micropeak_test.c -lm + +ao_fat_test: ao_fat_test.c ao_fat.c ao_bufio.c + cc $(CFLAGS) -o $@ ao_fat_test.c -lssl diff --git a/src/test/ao_aprs_test.c b/src/test/ao_aprs_test.c index 3b31f2d3..dd5eac4d 100644 --- a/src/test/ao_aprs_test.c +++ b/src/test/ao_aprs_test.c @@ -107,7 +107,7 @@ int main(int argc, char **argv) } void -ao_radio_send_lots(ao_radio_fill_func fill) +ao_radio_send_aprs(ao_radio_fill_func fill) { int16_t len; uint8_t done = 0; diff --git a/src/test/ao_fat_test.c b/src/test/ao_fat_test.c new file mode 100644 index 00000000..d1309024 --- /dev/null +++ b/src/test/ao_fat_test.c @@ -0,0 +1,565 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <getopt.h> +#include <math.h> +#include <unistd.h> +#include <fcntl.h> +#include <openssl/md5.h> + +#define AO_FAT_TEST + +void +ao_mutex_get(uint8_t *mutex) +{ +} + +void +ao_mutex_put(uint8_t *mutex) +{ +} + +void +ao_panic(uint8_t panic) +{ + printf ("panic %d\n", panic); + abort(); +} + +#define AO_PANIC_BUFIO 15 + +#define ao_cmd_success 0 + +uint8_t ao_cmd_status; +uint32_t ao_cmd_lex_u32; + +void +ao_cmd_decimal() +{ +} + +#define ao_cmd_register(x) + +struct ao_cmds { + void (*func)(void); + const char *help; +}; + +int fs_fd; + +uint64_t total_reads, total_writes; + +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data) +{ + ++total_reads; + lseek(fs_fd, block * 512, 0); + return read(fs_fd, data, 512) == 512; +} + +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data) +{ + ++total_writes; + lseek(fs_fd, block * 512, 0); + return write(fs_fd, data, 512) == 512; +} + +struct fs_param { + int fat; + int blocks; +} fs_params[] = { + { .fat = 16, .blocks = 16384 }, + { .fat = 32, .blocks = 16384 }, + { .fat = 16, .blocks = 65536 }, + { .fat = 32, .blocks = 65536 }, + { .fat = 16, .blocks = 1048576 }, + { .fat = 32, .blocks = 1048576 }, + { .fat = 0, .blocks = 0 }, +}; + +char *fs = "fs.fat"; +struct fs_param *param; + +void +ao_sdcard_init(void) +{ + char cmd[1024]; + + if (fs_fd) { + close(fs_fd); + fs_fd = 0; + } + snprintf(cmd, sizeof(cmd), "rm -f %s && mkfs.vfat -F %d -C %s %d", + fs, param->fat, fs, param->blocks); + if (system (cmd) != 0) { + fprintf(stderr, "'%s' failed\n", cmd); + exit(1); + } + fs_fd = open(fs, 2); + if (fs_fd < 0) { + perror (fs); + exit(1); + } +} + +#include "ao_bufio.c" +void +check_bufio(char *where) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + if (ao_bufio[b].busy) { + printf ("%s: buffer %d busy. block %d seqno %u\n", + where, b, ao_bufio[b].block, ao_bufio[b].seqno & 0xffff); + abort(); + } + } +} + + +void +check_fat(void); + +#include "ao_fat.c" + +/* Get the next cluster entry in the chain */ +static cluster_t +ao_fat_entry_raw_read(cluster_t cluster, uint8_t fat) +{ + sector_t sector; + cluster_offset_t offset; + uint8_t *buf; + cluster_t ret; + + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> SECTOR_SHIFT; + offset = cluster & SECTOR_MASK; + buf = _ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector); + if (!buf) + return 0; + if (fat32) + ret = get_u32(buf + offset); + else + ret = get_u16(buf + offset); + _ao_fat_sector_put(buf, 0); + return ret; +} + +void +dump_fat(void) +{ + int e; + + printf ("\n **** FAT ****\n\n"); + for (e = 0; e < number_cluster; e++) { + if ((e & 0xf) == 0x0) + printf ("%04x: ", e); + if (fat32) + printf (" %08x", ao_fat_entry_raw_read(e, 0)); + else + printf (" %04x", ao_fat_entry_raw_read(e, 0)); + if ((e & 0xf) == 0xf) + putchar ('\n'); + } + if (e & 0xf) + putchar('\n'); +} + +void +fat_list(void) +{ + dirent_t entry = 0; + struct ao_fat_dirent dirent; + + printf (" **** Root directory ****\n"); + while (ao_fat_readdir(&entry, &dirent)) { + printf ("%04x: %-8.8s.%-3.3s %02x %04x %d\n", + entry, + dirent.name, + dirent.name + 8, + dirent.attr, + dirent.cluster, + dirent.size); + } + + printf (" **** End of root directory ****\n"); +} + +void +fatal(char *msg, ...) +{ +// dump_fat(); +// fat_list(); + + va_list l; + va_start(l, msg); + vfprintf(stderr, msg, l); + va_end(l); + + abort(); +} + +void +check_fat(void) +{ + cluster_t e; + int f; + + for (e = 0; e < number_cluster; e++) { + cluster_t v = ao_fat_entry_raw_read(e, 0); + for (f = 1; f < number_fat; f++) { + cluster_t o = ao_fat_entry_raw_read(e, f); + if (o != v) + fatal ("fats differ at %08x (0 %08x %d %08x)\n", e, v, f, o); + } + } +} + +cluster_t +check_file(dirent_t dent, cluster_t first_cluster, dirent_t *used) +{ + cluster_t clusters = 0; + cluster_t cluster; + + if (!first_cluster) + return 0; + + for (cluster = first_cluster; + fat32 ? !AO_FAT_IS_LAST_CLUSTER(cluster) : !AO_FAT_IS_LAST_CLUSTER16(cluster); + cluster = ao_fat_entry_raw_read(cluster, 0)) + { + if (!_ao_fat_cluster_valid(cluster)) + fatal("file %d: invalid cluster %08x\n", dent, cluster); + if (used[cluster]) + fatal("file %d: duplicate cluster %08x also in file %d\n", dent, cluster, used[cluster]-1); + used[cluster] = dent; + clusters++; + } + return clusters; +} + +void +check_fs(void) +{ + dirent_t r; + cluster_t cluster, chain; + dirent_t *used; + uint8_t *dent; + + check_fat(); + + used = calloc(sizeof (dirent_t), number_cluster); + + for (r = 0; (dent = _ao_fat_root_get(r)); r++) { + cluster_t clusters; + offset_t size; + cluster_t first_cluster; + char name[11]; + + if (!dent) + fatal("cannot map dent %d\n", r); + memcpy(name, dent+0, 11); + first_cluster = get_u16(dent + 0x1a); + if (fat32) + first_cluster |= (cluster_t) get_u16(dent + 0x14) << 16; + size = get_u32(dent + 0x1c); + _ao_fat_root_put(dent, r, 0); + + if (name[0] == AO_FAT_DENT_END) { + break; + } + + clusters = check_file(r + 1, first_cluster, used); + if (size == 0) { + if (clusters != 0) + fatal("file %d: zero sized, but %d clusters\n", clusters); + } else { + if (size > clusters * bytes_per_cluster) + fatal("file %d: size %u beyond clusters %d (%u)\n", + r, size, clusters, clusters * bytes_per_cluster); + if (size <= (clusters - 1) * bytes_per_cluster) + fatal("file %d: size %u too small clusters %d (%u)\n", + r, size, clusters, clusters * bytes_per_cluster); + } + } + if (!fat32) { + for (; r < root_entries; r++) { + uint8_t *dent = _ao_fat_root_get(r); + if (!dent) + fatal("cannot map dent %d\n", r); + if (dent[0] != AO_FAT_DENT_END) + fatal("found non-zero dent past end %d\n", r); + _ao_fat_root_put(dent, r, 0); + } + } else { + check_file((dirent_t) -1, root_cluster, used); + } + + for (cluster = 0; cluster < 2; cluster++) { + chain = ao_fat_entry_raw_read(cluster, 0); + + if (fat32) { + if ((chain & 0xffffff8) != 0xffffff8) + fatal("cluster %d: not marked busy\n", cluster); + } else { + if ((chain & 0xfff8) != 0xfff8) + fatal("cluster %d: not marked busy\n", cluster); + } + } + for (; cluster < number_cluster; cluster++) { + chain = ao_fat_entry_raw_read(cluster, 0); + + if (chain != 0) { + if (used[cluster] == 0) + fatal("cluster %d: marked busy, but not in any file\n", cluster); + } else { + if (used[cluster] != 0) + fatal("cluster %d: marked free, but found in file %d\n", cluster, used[cluster]-1); + } + } +} + +#define NUM_FILES 100 +#define LINES_FILE 500000 + +uint32_t sizes[NUM_FILES]; + +unsigned char md5[NUM_FILES][MD5_DIGEST_LENGTH]; + +void +micro_test_fs(void) +{ + int8_t fd; + char name[] = "FOO "; + char buf[512]; + int len; + + printf ("write once\n"); + if ((fd = ao_fat_creat(name)) >= 0) { + ao_fat_write(fd, "hello world\n", 12); + ao_fat_close(fd); + } + + printf ("read first\n"); + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + len = ao_fat_read(fd, buf, sizeof(buf)); + write (1, buf, len); + ao_fat_close(fd); + } + + printf ("write again\n"); + if ((fd = ao_fat_creat(name)) >= 0) { + ao_fat_write(fd, "hi\n", 3); + ao_fat_close(fd); + } + + printf ("read again\n"); + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + len = ao_fat_read(fd, buf, sizeof(buf)); + write (1, buf, len); + ao_fat_close(fd); + } + + printf ("write 3\n"); + if ((fd = ao_fat_creat(name)) >= 0) { + int l; + char c; + + for (l = 0; l < 10; l++) { + for (c = ' '; c < '~'; c++) + ao_fat_write(fd, &c, 1); + c = '\n'; + ao_fat_write(fd, &c, 1); + } + ao_fat_close(fd); + } + + printf ("read 3\n"); + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + while ((len = ao_fat_read(fd, buf, sizeof(buf))) > 0) + write (1, buf, len); + ao_fat_close(fd); + } + + check_fs(); + printf ("all done\n"); +} + + +void +short_test_fs(void) +{ + int len; + int8_t fd; + char buf[345]; + + if ((fd = ao_fat_open("HELLO TXT",AO_FAT_OPEN_READ)) >= 0) { + printf ("File contents for HELLO.TXT\n"); + while ((len = ao_fat_read(fd, buf, sizeof(buf)))) + write(1, buf, len); + ao_fat_close(fd); + } + + if ((fd = ao_fat_creat("NEWFILE TXT")) >= 0) { + printf ("Create new file\n"); + for (len = 0; len < 2; len++) + ao_fat_write(fd, "hello, world!\n", 14); + ao_fat_seek(fd, 0, AO_FAT_SEEK_SET); + printf ("read new file\n"); + while ((len = ao_fat_read(fd, buf, sizeof (buf)))) + write (1, buf, len); + ao_fat_close(fd); + } + + check_fs(); +} + +void +long_test_fs(void) +{ + char name[12]; + int id; + MD5_CTX ctx; + unsigned char md5_check[MD5_DIGEST_LENGTH]; + char buf[337]; + int len; + int8_t fd; + uint64_t total_file_size = 0; + + total_reads = total_writes = 0; + + printf (" **** Creating %d files\n", NUM_FILES); + + memset(sizes, '\0', sizeof (sizes)); + for (id = 0; id < NUM_FILES; id++) { + sprintf(name, "D%07dTXT", id); + if ((id % ((NUM_FILES+49)/50)) == 0) { + printf ("."); fflush(stdout); + } + if ((fd = ao_fat_creat(name)) >= 0) { + int j; + char line[64]; + check_bufio("file created"); + MD5_Init(&ctx); + for (j = 0; j < LINES_FILE; j++) { + int len, ret; + sprintf (line, "Hello, world %d %d\r\n", id, j); + len = strlen(line); + ret = ao_fat_write(fd, line, len); + if (ret <= 0) + break; + total_file_size += ret; + MD5_Update(&ctx, line, ret); + sizes[id] += ret; + if (ret != len) + printf ("write failed %d\n", ret); + } + ao_fat_close(fd); + MD5_Final(&md5[id][0], &ctx); + check_bufio("file written"); + } + } + + printf ("\n **** Write IO: read %llu write %llu data sectors %llu\n", total_reads, total_writes, (total_file_size + 511) / 512); + + check_bufio("all files created"); + printf (" **** All done creating files\n"); + check_fs(); + + total_reads = total_writes = 0; + + printf (" **** Comparing %d files\n", NUM_FILES); + + for (id = 0; id < NUM_FILES; id++) { + uint32_t size; + sprintf(name, "D%07dTXT", id); + size = 0; + if ((id % ((NUM_FILES+49)/50)) == 0) { + printf ("."); fflush(stdout); + } + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + MD5_Init(&ctx); + while ((len = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + MD5_Update(&ctx, buf, len); + size += len; + } + ao_fat_close(fd); + MD5_Final(md5_check, &ctx); + if (size != sizes[id]) + fatal("file %d: size differs %d written %d read\n", + id, sizes[id], size); + if (memcmp(md5_check, &md5[id][0], sizeof (md5_check)) != 0) + fatal ("file %d: checksum failed\n", id); + check_bufio("file shown"); + } + } + printf ("\n **** Read IO: read %llu write %llu\n", total_reads, total_writes); +} + +char *params[] = { + "-F 16 -C %s 16384", + "-F 32 -C %s 16384", + "-F 16 -C %s 65536", + "-F 32 -C %s 65536", + "-F 16 -C %s 1048576", + "-F 32 -C %s 1048576", + NULL +}; + +void +do_test(void (*test)(void)) +{ + ao_fat_init(); + + check_bufio("top"); + _ao_fat_setup(); + + check_fs(); + check_bufio("after setup"); + (*test)(); + ao_fat_unmount(); +} + +int +main(int argc, char **argv) +{ + int p; + + if (argv[1]) + fs = argv[1]; + + for (p = 0; fs_params[p].fat; p++) { + param = &fs_params[p]; + + do_test(micro_test_fs); + do_test(short_test_fs); + do_test(long_test_fs); + } + unlink (fs); + + return 0; +} diff --git a/src/test/ao_flight_test.c b/src/test/ao_flight_test.c index cdd1f236..99bed7ee 100644 --- a/src/test/ao_flight_test.c +++ b/src/test/ao_flight_test.c @@ -35,7 +35,7 @@ #define AO_MS_TO_SPEED(ms) ((int16_t) ((ms) * 16)) #define AO_MSS_TO_ACCEL(mss) ((int16_t) ((mss) * 16)) -#if MEGAMETRUM +#if TELEMEGA #define AO_ADC_NUM_SENSE 6 #define HAS_MS5607 1 #define HAS_MPU6000 1 @@ -195,7 +195,7 @@ struct ao_cmds { #define ao_xmemcmp(d,s,c) memcmp(d,s,c) #define AO_NEED_ALTITUDE_TO_PRES 1 -#if MEGAMETRUM +#if TELEMEGA #include "ao_convert_pa.c" #include <ao_ms5607.h> struct ao_ms5607_prom ms5607_prom; @@ -333,7 +333,7 @@ ao_insert(void) #else double accel = 0.0; #endif -#if MEGAMETRUM +#if TELEMEGA double height; ao_ms5607_convert(&ao_data_static.ms5607_raw, &ao_data_static.ms5607_cooked); @@ -373,7 +373,7 @@ ao_insert(void) if (!ao_summary) { printf("%7.2f height %8.2f accel %8.3f " -#if MEGAMETRUM +#if TELEMEGA "roll %8.3f angle %8.3f qangle %8.3f " "accel_x %8.3f accel_y %8.3f accel_z %8.3f gyro_x %8.3f gyro_y %8.3f gyro_z %8.3f " #endif @@ -381,7 +381,7 @@ ao_insert(void) time, height, accel, -#if MEGAMETRUM +#if TELEMEGA ao_mpu6000_gyro(ao_sample_roll_angle) / 100.0, ao_mpu6000_gyro(ao_sample_angle) / 100.0, ao_sample_qangle, @@ -555,7 +555,7 @@ int32(uint8_t *bytes, int off) static int log_format; -#if MEGAMETRUM +#if TELEMEGA static double ao_vec_norm(double x, double y, double z) @@ -774,7 +774,7 @@ ao_sleep(void *wchan) for (;;) { if (ao_records_read > 2 && ao_flight_state == ao_flight_startup) { -#if MEGAMETRUM +#if TELEMEGA ao_data_static.mpu6000 = ao_ground_mpu6000; #else ao_data_static.adc.accel = ao_flight_ground_accel; @@ -800,8 +800,8 @@ ao_sleep(void *wchan) if (words[nword] == NULL) break; } -#if MEGAMETRUM - if (log_format == AO_LOG_FORMAT_MEGAMETRUM && nword == 30 && strlen(words[0]) == 1) { +#if TELEMEGA + if (log_format == AO_LOG_FORMAT_TELEMEGA && nword == 30 && strlen(words[0]) == 1) { int i; struct ao_ms5607_value value; @@ -885,7 +885,7 @@ ao_sleep(void *wchan) continue; } #else - if (nword == 4 && log_format != AO_LOG_FORMAT_MEGAMETRUM) { + if (nword == 4 && log_format != AO_LOG_FORMAT_TELEMEGA) { type = words[0][0]; tick = strtoul(words[1], NULL, 16); a = strtoul(words[2], NULL, 16); @@ -1002,7 +1002,7 @@ ao_sleep(void *wchan) if (type != 'F' && !ao_flight_started) continue; -#if MEGAMETRUM +#if TELEMEGA (void) a; (void) b; #else |