they could be, there is no neighbour or ARP cache, there is no
storage for servers for dns, ntp, sip-proxy.
+* tic55x's int.c driver: work out conditions for hibernation sleep.
+
+* tic55x's PLL: get better performance by increasing clock rate. This
+ will influence other forms of timing, such as RAM, Network and Codec.
+
* Linux PC (Windows might work as a sub-optimal substitute)
* useful if available: oscilloscope, logic analyser
-The multimeter should be sensitive enough to measure through
+The **multimeter** should be sensitive enough to measure through
a device without damaging it. Its beep is very helpful because
it allows you to drag one pin over the many pins of a large chip
to find the connection to another point in the circuit.
+As an electronics engineer, I usually have a gut feeling how things
+are wired. Using the multimeter, I could establish these things.
+I would switch off the device, set it in the ohmic beeping mode, place
+one probe of the multimeter on the pin I wanted to trace and then
+poke around. Where I suspected connectivity to a chip, I would pull
+the second probe along the many pins of such a chip. I would make a
+complete round before zooming in on a single beep -- sometimes you
+expect to find a signal but you are actually measuring GND, and that
+intuition is delivered audibly as several beeps on the same chip. If
+you hit a single pin, always double check the pins around the suspect,
+as you will sometimes be probing two pins instead of just one.
+
+It's not much fun counting pins, but there are usually markings on
+the PCB's silk screen for every fifth pin. Always do this work in
+broad daylight; nothing can beat the Sun for proper illumination.
+Closing one eye may help, and using a magnifying glass may help too.
+
+And yes... I would love to have probes with one conductive side and
+one isolated side, so I could poke it in between two pins instead
+of balancing it on top of one while moving something else around.
+Or, better even, it would be good to have a probe shaped like a
+cogwheel, that would rotate over the pins and show which one is the
+connected pin. But those are just silly dreams.
+
+An **oscilloscope** is useful for testing analog signals; in a digital
+phone, these are mostly limited to clock signals and sound I/O; in
+an ATA there would be a lot more use for an oscilloscope. Since
+the signals are not always repeating, a digital scope is the best
+option. These often come with a logic analyser as an optional extra.
+
+A **logic analyser** can save a lot of work (an excellent reason to
+finally get one!) because it makes it possible to observe signals
+that are being sent to unknown or uncertain components.
+For reverse engineering the BT200, a logic analyser was useful for
+confirming that the LCD driver chip was indeed (acting like) a
+HT1621D, without having to program a driver and be left with the
+uncertainty if the guessed chip was wrong, or the frequencies.
+Also, it helped to read out the configuration codes sent to the
+LCD driver. Later on, it showed that the LCD driver that I wrote
+did not behaving well on this bus, even if it was not visible;
+there were small glitches in the signal as a result of writing
+16-bit values to an 8-bit bus; the high part of the word was filled
+with zeroes and would make observed signals low for a very short
+time where they were supposed to remain high. Finally,
+communication with the codec (sound I/O chip) can easily be viewed
+with a logic analyser. In an ATA, a few more components apply.
+
+.. figure:: pix/ht162x.png
+ :scale: 200
+ :alt: Logic Analyser at work
+
+ The logic analyser at work. Shown is the LCD driver of
+ the BT200 phone, for which we guessed it could be a
+ HT162x, based on searching the connector's pin markings
+ on the PCB. D4 is the DATA signal, D3 is CS, D2 is
+ the RD clock (unconnected in the phone), D1 is the WR
+ clock. At every rising edge of D1, the value of D4 is
+ checked into the LCD. Note that D1 is always stable at
+ that time, which does not undermine this behaviour from
+ the assumed datasheet. The period of a command is marked
+ by D3. 12 bits makes it probable that this is a HT162x,
+ and the command confirms that: 100.1110.0011.0 is the
+ LCD command to set NORMAL mode (not TEST mode) and this
+ makes sense, also in the combination with other commands
+ observed. This confirmed the HT162x family's behaviour.
+ Based on the empty holes that once held a crystal and
+ the number of segments on the display this was further
+ refined, and the LCD driver had to be a HT1621D chip.
+
Understanding your hardware
===========================
* A codec chip. These are a bit like embedded sound cards; they can
be accessed over a protocol like SPI to interact with microphones,
speakers and so on. They usually include analog electronics such
- as amplifiers.
+ as amplifiers and anti-aliasing filters.
Don't forget to check the PCB's bottom; there may be components on
both sides!
In ``make menuconfig``, select the firmware function ``Test keyboard / display``
that will scan the keyboard and write its findings to the display.
-TODO: To build this, you would normally have to write a timer setup and
-interrupt service routine to handle ``bottom_time()`` and
-``bottom_set_timer_set()`` --do not forget to return the old setting for
-the latter-- in addition to the previously written ``bottom_led_set()``
-function. If you care to play with it, edit
-``src/function/develtest/keys2display.c`` but be sure to recover the original
-before you submit your work.
+To build this, you would normally have to write ``bottom_keyboard_scan()``,
+``bottom_hook_scan()``, ``bottom_show_fixed_msg()``, ``bottom_show_period``,
+``bottom_show_ip4()``, ``bottom_show_ip6()``, ``bottom_show_close_level()``.
+Note that the ``bottom_show_`` routines need not all be implemented fully;
+they exist to permit the top layer to offer information to be laid out in
+a format that is optimal for the target phone. Do take note that there are
+levels of information, to ensure that shared space on the display is made
+available for higher priority data if need be.
If this works, you are able to scan keys and write texts on the display.
In ``make menuconfig``, select the firmware function ``Test network``
that will provide an LLC-based console over ethernet.
-TODO: To build this, you would normally have to write a timer setup and
-interrupt service routine to handle ``bottom_time()`` and
-``bottom_set_timer_set()`` --do not forget to return the old setting for
-the latter-- in addition to the previously written ``bottom_led_set()``
-function. If you care to play with it, edit
-``src/function/develtest/keys2display.c`` but be sure to recover the original
-before you submit your work.
+To build this, you would normally have to write a driver for the network
+chip. You would need to handle interrupts from the network interface to
+permit a smoothe operation of the phone. As part of the driver, you may
+have to locate where the MAC address is in flash -- as that address is
+sometimes loaded from an EEPROM, but on an embedded device it is usually
+cheaper to have the firmware handle that.
+
+If this works, you are able to send and receive information between the
+CPU and the network.
+
+
+Build 5: Echo unit
+------------------
-If this works, you are able to use the network; your next build would
-be the bootloader, which is the first real application that goes beyond
-developer toys.
+In ``make menuconfig``, select the firmware function ``Test sound``
+that will echo anything it hears back after a two-second delay.
+This is the simplest test for the complete sound interface It reads
+input signals and sends them back after a delay. This loop involves
+the microphone for sound reception, DMA for receiving the microphone
+input, the CPU and RAM for the delay, DMA for sending the speaker output,
+and finally the speaker output for playing the sound back at you.
-Build 5: Bootloader
+The echo test can toggle between handset mode and speakerphone mode,
+if the phone's hardware supports it. It will see if the phone is
+off-hook to determine the mode. Other interfaces (headset and line)
+are not tested, as they are not as vital, and will be much simpler
+to debug after one or two sound paths have shown to work.
+
+To build this, you would normally have to write a driver for the
+sound chip or codec. This is best done through DMA, as that is the
+best way of assuring a constant playing rate for samples. An
+approach based on interrupts would suffer from other interrupts and
+critical regions that temporarily disable interrupts.
+
+If this works, the sound parts of your phone appears to be working
+all the way from the CPU to the human operating the phone; your next
+build would be the bootloader, which is the first real application
+that goes beyond developer toys.
+
+
+Build 6: Bootloader
-------------------
This is the first firmware function that actually reflects a useful
access to the flash memory, even for writing. Your best bet would be
to first download the entire contents of flash, of course.
+The bootloader is the first target that could actually be useful to
+burn into the phone's flash. Ideally, this would be done in such a
+place that the original firmware can be re-inserted if so required;
+end-users will be more likely to try your firmerware if it is also
+possible to move back to the original situation that they had. It
+could be useful if the 0cpm Bootloader could continue to be used
+even under the original firmware, as it gives more control to the
+end-user. Please document clearly if and how this works on the
+target platform you are porting to.
+
Note that the bootloader will only run as long as the phone is off-hook;
if it is on-hook during boot, it will skip to the actual application.
Given that development machines are usually open, the horn is usually
skipped. This also rules out various abuse patterns.
-Build 6: SIP phone / doorbell / alarm clock
--------------------------------------------
+Build 7: SIP phone / doorbell / alarm clock / ...
+-------------------------------------------------
You can now select the firmware functions that you are really after.
TODO: bool bottom_phone_is_offhook (void);
TODO: TIME_MSEC, TIME_SEC, TIME_MIN, TIME_HOUR, TIME_DAY
TODO: TIME_BEFORE(a,b)
+TODO: bottom_show_xxx() series of calls
+TODO: bottom_xxx_scan() and top_xxx() from develtests
+TODO: network_can_xxx() will be called before 1500 bytes free but then certainly called again before done
+TODO: bottom_network_send/_recv() accept 32-bit transfers
----------------------------------------------
Kernel API between Phone Bottom and Top Halves
--- /dev/null
+/* Console logging facility
+ *
+ * This implements logging over an LLC console, where supported.
+ * Messages are written to the log when sent as bottom_printf (fmt, ...)
+ * and will always be prefixed with __FILE__ and __LINE__ information.
+ * The implementation is through a bottom_console_printf() call.
+ * If the console is not compiled in, there will be no code generated
+ * for such calls.
+ *
+ * For portability to all platforms, there must always be at least one
+ * argument, even if it is not used. There are ways to work around this
+ * constraint, as described on the link below, but relying on it would
+ * be asking for trouble when targetting all kinds of embedded compilers.
+ * http://blog.mellenthin.de/archives/2010/10/26/portable-__va_args__-macros-for-linux-hp-ux-solaris-and-aix/
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+#ifndef HEADER_CONSOLE
+#define HEADER_CONSOLE
+
+#ifndef CONFIG_FUNCTION_DEVEL_NETCONSOLE
+ inline void bottom_printf (char *fmt, ...) { ; }
+#else
+ void bottom_console_vprintf (char *fmt, va_list argh);
+ void bottom_console_printf (char *fmt, ...);
+ inline void bottom_printf (char *fmt, ...) {
+ va_list argh;
+ va_start (argh, fmt);
+ // bottom_console_printf ("%s:%d ", __FILE__, __LINE__);
+ bottom_console_vprintf (fmt, argh);
+ va_end (argh);
+ }
+#endif
+
+#endif
extern volatile uint8_t kbdisp;
asm ("_kbdisp .set 0x666666");
+/* The network interface KSZ8842-16 is booted without EEPROM, so it
+ * has no knowledge of anything -- specifically, no MAC address.
+ * It has been hard-wired to a 16-bit interface, but it is addressed
+ * from the DSP chip as a 32-bit interface. The top half of the
+ * values read are therefore jibberish. Yes, silly is the right word.
+ */
+#pragma FAR(ksz8842_16)
+extern volatile uint32_t ksz8842_16 [];
+asm ("_ksz8842_16 .set 0x012340");
+
+/* The following are the low-level routines for accessing this
+ * particular use of the KSZ8842-16. They are used in other
+ * routines, and the definitions in <bottom/ksz8842.h>
+ */
+#define kszreg16(N) ksz8842_16[(N)>>1]
+
+inline uint16_t kszmap16(uint16_t x) {
+ return (x << 8) | (x >> 8);
+}
+
+inline uint32_t kszmap32(uint32_t x) {
+ return (x << 24) | (x >> 24) | ((x << 8) & 0x00ff000) | ((x >> 8) & 0x0000ff00);
+}
+
+inline uint16_t ksz_memget16 (uint8_t *mem) {
+ return (mem [0] << 8) | (mem [1] & 0xff);
+}
+
+inline void ksz_memset16 (uint16_t ksz, uint8_t *mem) {
+ mem [0] = ksz >> 8;
+ mem [1] = ksz & 0xff;
+}
+
+
+#pragma FAR(flash_16)
+extern volatile uint16_t flash_16 [];
+asm ("_flash_16 .set 0x200000");
+
#endif
--- /dev/null
+/* Micrel KSZ8842 includes.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+#ifndef HEADER_KSZ8842
+#define HEADER_KSZ8842
+
+
+typedef uint16_t kszint16_t;
+typedef uint32_t kszint32_t;
+
+
+#define KSZ8842_BANKx_BSR kszreg16(14)
+
+#define KSZ8842_BANK0_BAR kszreg16(0)
+#define KSZ8842_BANK2_MARL kszreg16(0)
+#define KSZ8842_BANK2_MARM kszreg16(2)
+#define KSZ8842_BANK2_MARH kszreg16(4)
+#define KSZ8842_BANK0_BESR kszreg16(6)
+
+#define KSZ8842_BANK16_TXCR kszreg16(0)
+#define KSZ8842_BANK16_TXSR kszreg16(2)
+#define KSZ8842_BANK16_RXCR kszreg16(4)
+#define KSZ8842_BANK16_TXMIR kszreg16(8)
+#define KSZ8842_BANK16_RXMIR kszreg16(10)
+#define KSZ8842_BANK17_TXQCR kszreg16(0)
+#define KSZ8842_BANK17_RXQCR kszreg16(2)
+#define KSZ8842_BANK17_TXFDPR kszreg16(4)
+#define KSZ8842_BANK17_RXFDPR kszreg16(6)
+#define KSZ8842_BANK17_QDRL kszreg16(8)
+#define KSZ8842_BANK17_QDRH kszreg16(10)
+#define KSZ8842_BANK18_IER kszreg16(0)
+#define KSZ8842_BANK18_ISR kszreg16(2)
+#define KSZ8842_BANK18_RXSR kszreg16(4)
+#define KSZ8842_BANK18_RXBC kszreg16(6)
+
+#define KSZ8842_BANK32_SIDER kszreg16(0)
+
+#define KSZ8842_BANK45_P1MBSR kszreg16(2)
+#define KSZ8842_BANK46_P2MBSR kszreg16(2)
+
+#define KSZ8842_BANK_SET(x) (KSZ8842_BANKx_BSR = kszmap16 (x))
+
+
+#define REGVAL_KSZ8842_ISR_LCIS 0x8000
+#define REGVAL_KSZ8842_ISR_TXIS 0x4000
+#define REGVAL_KSZ8842_ISR_RXIS 0x2000
+#define REGVAL_KSZ8842_ISR_RXOIS 0x0800
+#define REGVAL_KSZ8842_ISR_TXPSIE 0x0200
+#define REGVAL_KSZ8842_ISR_RXPSIE 0x0100
+#define REGVAL_KSZ8842_ISR_RXEFIE 0x0080
+
+#define REGVAL_KSZ8842_TXCR_TXFCE 0x0008
+#define REGVAL_KSZ8842_TXCR_TXPE 0x0004
+#define REGVAL_KSZ8842_TXCR_TXCE 0x0002
+#define REGVAL_KSZ8842_TXCR_TXE 0x0001
+
+#define REGVAL_KSZ8842_RXCR_RXFCE 0x0400
+#define REGVAL_KSZ8842_RXCR_RXEFE 0x0200
+#define REGVAL_KSZ8842_RXCR_RXBE 0x0080
+#define REGVAL_KSZ8842_RXCR_RXME 0x0040
+#define REGVAL_KSZ8842_RXCR_RXUE 0x0020
+#define REGVAL_KSZ8842_RXCR_RXRA 0x0010
+#define REGVAL_KSZ8842_RXCR_RXSCE 0x0008
+#define REGVAL_KSZ8842_RXCR_RXE 0x0001
+
+#define REGVAL_KSZ8842_RXSR_RXFV 0x8000
+#define REGVAL_KSZ8842_RXSR_RXBF 0x0080
+#define REGVAL_KSZ8842_RXSR_RXMF 0x0040
+#define REGVAL_KSZ8842_RXSR_RXUF 0x0020
+#define REGVAL_KSZ8842_RXSR_RXFT 0x0008
+#define REGVAL_KSZ8842_RXSR_RXTL 0x0004
+#define REGVAL_KSZ8842_RXSR_RXRF 0x0002
+#define REGVAL_KSZ8842_RXSR_RXCE 0x0001
+
+#define REGVAL_KSZ8842_TXQCR_TXETF 0x0001
+#define REGVAL_KSZ8842_RXQCR_RXRRF 0x0001
+
+#define REGVAL_KSZ8842_PxMBSR_ONLINE 0x0004
+
+#define CTLVAL_KSZ8842_TXIC 0x8000
+
+
+#endif
+
+
+/* Map KSZ8842 network data to or from general purpose memory */
+void ksz_memset16 (kszint16_t ksz, uint8_t *mem);
+kszint16_t ksz_memget16 (uint8_t *mem);
+
+
+/* Setup the KSZ8842 chip */
+void ksz8842_setup_network (void);
+
+/* KSZ8842 interrupt handler, _but_ compiled as a plain function.
+ * Retrieve the ISR register and handle each interrupt reason.
+ */
+void ksz8842_interrupt_handler (void);
asm ("_IVPD .set 0x0049");
asm ("_IVPH .set 0x004a");
+extern volatile bool tic55x_top_has_been_interrupted;
+
void setup_interrupts (void);
/* General Purpose I/O registers */
asm ("_IODIR .set 0x3400");
asm ("_IODATA .set 0x3401");
+/* EMIF and CE space control registers */
+extern volatile uint16_t ioport EGCR1, EGCR2;
+extern volatile uint16_t ioport CE0_1, CE0_2, CE1_1, CE1_2, CE2_1, CE2_2, CE3_1, CE3_2;
+extern volatile uint16_t ioport CE0_SEC1, CE0_SEC2, CE1_SEC1, CE1_SEC2,
+ CE2_SEC1, CE2_SEC2, CE3_SEC1, CE3_SEC2;
+asm ("_EGCR1 .set 0x0800");
+asm ("_EGCR2 .set 0x0801");
+asm ("_CE1_1 .set 0x0802");
+asm ("_CE1_2 .set 0x0803");
+asm ("_CE0_1 .set 0x0804");
+asm ("_CE0_2 .set 0x0805");
+asm ("_CE2_1 .set 0x0808");
+asm ("_CE2_2 .set 0x0809");
+asm ("_CE3_1 .set 0x080a");
+asm ("_CE3_2 .set 0x080b");
+asm ("_CE1_SEC1 .set 0x0822");
+asm ("_CE1_SEC2 .set 0x0823");
+asm ("_CE0_SEC1 .set 0x0824");
+asm ("_CE0_SEC2 .set 0x0825");
+asm ("_CE2_SEC1 .set 0x0828");
+asm ("_CE2_SEC2 .set 0x0829");
+asm ("_CE3_SEC1 .set 0x082a");
+asm ("_CE3_SEC2 .set 0x082b");
+
/* Pin Control Registers for McBSP0, McBSP1 */
extern volatile uint16_t ioport PCR0, PCR1;
asm ("_PCR0 .set 0x2812");
#define REGBIT_PICR_TIM1 1
#define REGBIT_PICR_TIM0 0
+/* PLL configuration registers */
+extern volatile uint16_t ioport PLLCSR, PLLM, PLLDIV1, PLLDIV2, PLLDIV3;
+asm ("_PLLCSR .set 0x1c80");
+asm ("_PLLM .set 0x1c88");
+asm ("_PLLDIV1 .set 0x1c8c");
+asm ("_PLLDIV2 .set 0x1c8e");
+asm ("_PLLDIV3 .set 0x1c90");
+
+#define REGVAL_PLLM_TIMES_2 0x0002
+#define REGVAL_PLLM_TIMES_3 0x0003
+#define REGVAL_PLLM_TIMES_4 0x0004
+#define REGVAL_PLLM_TIMES_5 0x0005
+#define REGVAL_PLLM_TIMES_6 0x0006
+#define REGVAL_PLLM_TIMES_7 0x0007
+#define REGVAL_PLLM_TIMES_8 0x0008
+#define REGVAL_PLLM_TIMES_9 0x0009
+#define REGVAL_PLLM_TIMES_10 0x000a
+#define REGVAL_PLLM_TIMES_11 0x000b
+#define REGVAL_PLLM_TIMES_12 0x000c
+#define REGVAL_PLLM_TIMES_13 0x000d
+#define REGVAL_PLLM_TIMES_14 0x000e
+#define REGVAL_PLLM_TIMES_15 0x000f
+
+#define REGVAL_PLLCSR_PLLEN 0x0001
+#define REGVAL_PLLCSR_PLLRST 0x0008
+#define REGVAL_PLLCSR_STABLE 0x0040
+
+#define REGVAL_PLLDIVx_DxEN 0x8000
+#define REGVAL_PLLDIVx_PLLDIVx_1 0x0000
+#define REGVAL_PLLDIVx_PLLDIVx_2 0x0001
+#define REGVAL_PLLDIVx_PLLDIVx_4 0x0003
+
#endif
-objs-bottom-$(CONFIG_TARGET_GRANDSTREAM_BT20x) += src/driver/tic55x/grandstream-bt20x.o src/driver/ht162x.o
+objs-bottom-$(CONFIG_TARGET_GRANDSTREAM_BT20x) += src/driver/tic55x/grandstream-bt20x.o src/driver/ht162x.o src/driver/ksz8842.o
#TODO: replace rts55.lib with bootstrapping in src/driver/tic55x/trampoline.o
--- /dev/null
+/* Micrel KSZ8842-16 drivers. Probably suitable for -32 as well.
+ *
+ * This driver uses a few low-level routines that should be part
+ * of the architecture definition of a particular phone:
+ * - kszmap16() maps 16-bit values between CPU and KSZ endianity
+ * - kszmap32() maps 32-bit values between CPU and KSZ endianity
+ * - kszreg16(N) is an l-value for 16-bit register N
+ * The registers of this chip are defined in terms of kszreg16()
+ *
+ * The kszmap functions are their own inverse functions. They are
+ * not applied automatically to the registers; in fact, it is more
+ * efficient to apply them to what is applied to the registers, as
+ * the mappings may be efficiently removed. For instance, in
+ *
+ * if (KSZ8842_BANK18_ISR & kszmap16(REGVAL_KSZ8842_ISR_LCIS)) ...
+ *
+ * the kszmap16 would not cost anything, as it can be applied at
+ * compile time to the REGVAL_KSZ8842_ISR_LCIS constant. Variables
+ * that contain values mapped like this would be of type kszint16_t
+ * or kszint32_t; the same as uint16_t and uint32_t, respectively.
+ *
+ * Note that KSZ8842_BANK_SET (N) uses a "normal" uint16_t N.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <config.h>
+#include <bottom/ksz8842.h>
+
+
+/* What the software knows about the online state of the chip */
+static bool online_status = false;
+
+
+/* KSZ8842 interrupt handler, _but_ compiled as a plain function.
+ * Retrieve the ISR register and handle each interrupt reason.
+ */
+void ksz8842_interrupt_handler (void) {
+ kszint16_t curbank;
+ kszint16_t ifr;
+
+ //
+ // Remember the bank that was active before the interrupt
+ curbank = KSZ8842_BANKx_BSR;
+
+ //
+ // Get hold of the interrupt flags, and clear them
+ KSZ8842_BANK_SET (18);
+ ifr = KSZ8842_BANK18_ISR;
+ KSZ8842_BANK18_ISR = ifr;
+
+ //
+ // See if the link status has changed
+ if (ifr & kszmap16 (REGVAL_KSZ8842_ISR_LCIS)) {
+ // Link change
+ bool either;
+ kszint16_t pxmbsr;
+ KSZ8842_BANK_SET (45);
+ pxmbsr = KSZ8842_BANK45_P1MBSR;
+ KSZ8842_BANK_SET (46);
+ pxmbsr |= KSZ8842_BANK46_P2MBSR;
+ either = (pxmbsr & kszmap16 (REGVAL_KSZ8842_PxMBSR_ONLINE)) != 0;
+ if (either != online_status) {
+ online_status = either;
+ if (online_status) {
+ top_network_online ();
+ } else {
+ top_network_offline ();
+ }
+ }
+ }
+
+ //
+ // See if more frames can be sent or received
+ if (ifr & kszmap16 (REGVAL_KSZ8842_ISR_TXIS)) {
+ top_network_can_send ();
+ }
+ if (ifr & kszmap16 (REGVAL_KSZ8842_ISR_RXIS)) {
+ top_network_can_recv ();
+ }
+
+ //
+ // Ignore a few error conditions after resetting their interrupt flag
+ // kszmap16 (REGVAL_KSZ8842_ISR_RXOIS): RX overrun status
+ // kszmap16 (REGVAL_KSZ8842_ISR_TXPSIE): TX stopped
+ // kszmap16 (REGVAL_KSZ8842_ISR_RXPSIE): RX stopped
+ // kszmap16 (REGVAL_KSZ8842_ISR_RXEFIE): RX frame error
+
+ //
+ // Recover the bank that was active before the interrupt
+ KSZ8842_BANKx_BSR = curbank;
+}
+
+
+/* Try to send a network packet */
+bool bottom_network_send (uint8_t *pkt, uint16_t pktlen) {
+ uint16_t txmem;
+ uint16_t pktidx;
+ KSZ8842_BANK_SET (16);
+ txmem = kszmap16 (KSZ8842_BANK16_TXMIR);
+ if (txmem < (pktlen + 4)) {
+ return false;
+ }
+ KSZ8842_BANK_SET (17);
+ //
+ // Hold while previous send data is being queued
+ while (KSZ8842_BANK17_TXQCR & kszmap16 (REGVAL_KSZ8842_TXQCR_TXETF)) {
+ /* Yes, this is busy waiting. It should not take long.
+ * Usually, all work will have been done when we get back here.
+ * If not, returning false could cause the network to block.
+ * So it is better to do busy waiting for a short while.
+ */
+ ;
+ }
+ //
+ // First send packet metadata: control, bytecount
+ // TODO: It is possible to set a control-ID in QDRL, see Table 4
+ KSZ8842_BANK17_QDRL = kszmap16 (CTLVAL_KSZ8842_TXIC);
+ KSZ8842_BANK17_QDRH = kszmap16 (pktlen);
+ //
+ // Now write out the data itself
+ pktidx = 0;
+ while (pktidx < pktlen) {
+ KSZ8842_BANK17_QDRL = ksz_memget16 (pkt + pktidx + 0);
+ KSZ8842_BANK17_QDRH = ksz_memget16 (pkt + pktidx + 2);
+ pktidx += 4;
+ }
+ //
+ // Indicate that the data has all been written
+ KSZ8842_BANK17_TXQCR |= kszmap16 (REGVAL_KSZ8842_TXQCR_TXETF);
+ return true;
+}
+
+
+/* Try to receive a network packet */
+bool bottom_network_recv (uint8_t *pkt, uint16_t *pktlen) {
+ bool ok = true;
+ uint16_t rxlen;
+ uint16_t rxdata;
+ //
+ // Hold until previous recv data has been skipped
+ KSZ8842_BANK_SET (17);
+ while (KSZ8842_BANK17_RXQCR & kszmap16 (REGVAL_KSZ8842_RXQCR_RXRRF)) {
+ /* Yes, this is busy waiting. It should not take long.
+ * Usually, all work will have been done when we get back here.
+ * If not, returning false could cause the network to block.
+ * So it is better to do busy waiting for a short while.
+ */
+ ;
+ }
+ //
+ // Is there a frame ready to be picked up?
+ // Note that only correct frames are welcomed.
+ KSZ8842_BANK_SET (18);
+ if ((KSZ8842_BANK18_RXSR & kszmap16 (REGVAL_KSZ8842_RXSR_RXFV)) == 0) {
+ return false;
+ }
+ //
+ // Check if the frame length does not exceed the available buffer
+ rxlen = (kszmap16 (KSZ8842_BANK18_RXBC) & 0x03ff);
+ ok = ok && (rxlen >= 6 + 6 + 2);
+ ok = ok && (rxlen <= *pktlen);
+ KSZ8842_BANK_SET (17);
+ if (ok) {
+ uint16_t pktidx = 0;
+ *pktlen = rxlen;
+ //
+ // Receive the header info (just skip it)
+ rxdata = KSZ8842_BANK17_QDRL; // Drop; already got RXSR
+ rxdata = KSZ8842_BANK17_QDRH; // Drop; already got rxlen
+ //
+ // Receive regular data in 4-byte blocks (possible overshoot up to 3)
+ while (pktidx < rxlen) {
+ rxdata = KSZ8842_BANK17_QDRL;
+ ksz_memset16 (rxdata, pkt + pktidx + 0);
+ rxdata = KSZ8842_BANK17_QDRH;
+ ksz_memset16 (rxdata, pkt + pktidx + 2);
+ pktidx += 4;
+ }
+ }
+ //
+ // Indicate that the data has all been read
+ KSZ8842_BANK17_RXQCR |= kszmap16 (REGVAL_KSZ8842_RXQCR_RXRRF);
+ //
+ // Return whether a good packet was actually received
+ return ok;
+}
+
+
+/* Setup the KSZ8842 chip */
+void ksz8842_setup_network (void) {
+ KSZ8842_BANK_SET (2);
+ // TODO: Where is the MAC address stored in Flash?
+ KSZ8842_BANK2_MARH = kszmap16 (0x000b); // 00:0b:__:__:__:__
+ KSZ8842_BANK2_MARM = kszmap16 (0x8219); // __:__:82:19:__:__
+ KSZ8842_BANK2_MARL = kszmap16 (0xa0f4); // __:__:__:__:a0:f4
+ KSZ8842_BANK_SET (17);
+ KSZ8842_BANK17_TXFDPR = kszmap16 (0x4000); // auto-inc TXQ ptr at 0
+ KSZ8842_BANK17_RXFDPR = kszmap16 (0x4000); // auto-inc RXQ ptr at 0
+ KSZ8842_BANK_SET (32);
+ KSZ8842_BANK32_SIDER |= kszmap16 (0x0001); // Switch enable
+ KSZ8842_BANK_SET (16);
+ KSZ8842_BANK16_TXCR =
+ kszmap16 (REGVAL_KSZ8842_TXCR_TXPE) |
+ kszmap16 (REGVAL_KSZ8842_TXCR_TXCE) |
+ kszmap16 (REGVAL_KSZ8842_TXCR_TXE);
+ KSZ8842_BANK16_RXCR =
+ kszmap16 (REGVAL_KSZ8842_RXCR_RXFCE) |
+ kszmap16 (REGVAL_KSZ8842_RXCR_RXBE) |
+ kszmap16 (REGVAL_KSZ8842_RXCR_RXME) |
+ kszmap16 (REGVAL_KSZ8842_RXCR_RXUE) |
+ kszmap16 (REGVAL_KSZ8842_RXCR_RXSCE) |
+ kszmap16 (REGVAL_KSZ8842_RXCR_RXE);
+ KSZ8842_BANK_SET (18);
+ KSZ8842_BANK18_IER |=
+ kszmap16 (REGVAL_KSZ8842_ISR_LCIS) |
+ kszmap16 (REGVAL_KSZ8842_ISR_TXIS) |
+ kszmap16 (REGVAL_KSZ8842_ISR_RXIS);
+ KSZ8842_BANK_SET (0); // TODO: needless
+}
+
+/* TODO -- the most-used network chip in SIP phones */
#include <0cpm/show.h>
#include <bottom/ht162x.h>
+#include <bottom/ksz8842.h>
+
+
+
+/******** EXTERNAL INTERRUPTS SERVICE ROUTINES ********/
+
+
+interrupt void tic55x_int0_isr (void) {
+ tic55x_top_has_been_interrupted = true;
+ ksz8842_interrupt_handler ();
+}
+
+interrupt void tic55x_int1 (void) {
+ tic55x_top_has_been_interrupted = true;
+ //TODO//
+}
+
+interrupt void tic55x_int2 (void) {
+ tic55x_top_has_been_interrupted = true;
+ //TODO//
+}
+
+interrupt void tic55x_int3 (void) {
+ tic55x_top_has_been_interrupted = true;
+ //TODO//
+}
/******** HT162x LCD DRIVER LOW-LEVEL FUNCTIONS ********/
/* See if the phone (actually, the horn) is offhook */
bool bottom_phone_is_offhook (void) {
// The hook switch is attached to GPIO pin 5
- // return ! tic55x_input_get_bit (REGADDR_IODATA, 5);
- return (IODATA & (1 << 5)) != 0;
+ return (IODATA & 0x20) != 0;
}
void main (void) {
uint16_t idx;
led_colour_t led = LED_STABLE_ON;
+ //
+ // PLL setup: Crystal is 16.384 MHz, increase that a bit
+ //TODO: Possible in theory, but doesn't run and sinusoidal in practice
+ // At lower PLLM factors, the sine is larger and clips to squarish
+ //TODO// PLLM = REGVAL_PLLM_TIMES_15;
+ //TODO// PLLCSR &= ~REGVAL_PLLCSR_PLLRST;
+ //TODO// while (!(PLLCSR & REGVAL_PLLCSR_STABLE)) {
+ //TODO// ;
+ //TODO// }
+ //TODO// PLLDIV1 = REGVAL_PLLDIVx_DxEN | REGVAL_PLLDIVx_PLLDIVx_4;
+ //TODO// PLLDIV2 = REGVAL_PLLDIVx_DxEN | REGVAL_PLLDIVx_PLLDIVx_4;
+ //TODO// PLLDIV3 = REGVAL_PLLDIVx_DxEN | REGVAL_PLLDIVx_PLLDIVx_4;
+ //TODO// PLLCSR |= REGVAL_PLLCSR_PLLEN;
+ //
+ // EMIF settings, see SPRU621F, section 2.12 on "EMIF Registers"
+ //
+ // EGCR1 = ...; // (defaults)
+ // EGCR2 = ...; // (defaults)
+ // CESCR1 = ...; // (defaults)
+ // CESCR2 = ...; // (defaults)
+ //
+ // CE0 selects the network interface
+ // CE0_1 = 0xff03; // DEFAULT 8-bit async (and defaults)
+ // Fail: CE0_1 = 0xc112; // 16-bit async, rd setup 2, rd strobe 1, rd hold 2
+ // Fail: CE0_2 = 0x20a2; // wr setup 2, wr strobe 2, wr hold 2, rd setup 2
+ CE0_1 = 0x3f2f;
+ CE0_2 = 0xffff;
+ // CE0_2 = ...; // (defaults)
+ // CE0_SC1 = ...; // (defaults)
+ // CE0_SC2 = ...; // (defaults)
+ //
+ // CE1 selects the flash chip
+ CE1_1 = 0xff13; // 16-bit async (and defaults)
+ // CE1_2 = ...; // (defaults)
+ // CE1_SC1 = ...; // (defaults)
+ // CE1_SC2 = ...; // (defaults)
+ //
+ // CE2 selects the SDRAM chips
+ CE2_1 = 0xff33; // 32-bit SDRAM (and defaults)
+ //TEST// CE2_1 = 0xff13; // 16-bit async (and defaults)
+ // CE2_2 = ...; // (defaults)
+ // CE2_SC1 = ...; // (defaults)
+ // CE2_SC2 = ...; // (defaults)
+ // Possible: SDC1, SDC2, SDRC1, SDRC2, SDX1, SDX2
+ //
+ // CE3 selects the D-flipflops for keyboard and LCD
+ CE3_1 = 0xff13; // 16-bit async (and defaults)
+ // CE3_2 = ...; // (defaults)
+ // CE3_SC1 = ...; // (defaults)
+ // CE3_SC2 = ...; // (defaults)
+ //
+ // Further initiation follows
+ //
for (idx = 0; idx < APP_LEVEL_COUNT; idx++) {
bt200_level_active [idx] = false;
}
tic55x_setup_timers ();
tic55x_setup_interrupts ();
ht162x_setup_lcd ();
+ ksz8842_setup_network ();
+ // Enable INT0..INT3
+ IER0 |= 0x080c;
+ IER1 |= 0x0001;
PCR0 = (1 << REGBIT_PCR_XIOEN) | (1 << REGBIT_PCR_RIOEN);
#if 0
.ref _c_int00
.ref _tic55x_no_isr
.ref _tic55x_tint0_isr
+ .ref _tic55x_int0_isr
_tic55x_bootld .set 0xff8000
isrmap0:
resetvect .ivec _c_int00, NO_RETA
nmi .ivec _tic55x_no_isr
-int0 .ivec _tic55x_no_isr
+int0 .ivec _tic55x_int0_isr
int2 .ivec _tic55x_no_isr
tint0 .ivec _tic55x_tint0_isr
rint0 .ivec _tic55x_no_isr
--- /dev/null
+/* TLV320AIC20/21/24/25/20K/24K codec driver
+ *
+ * The name "codec" is generally used for sound I/O chips that take a
+ * realtime stream of samples and map send them to speakers; and that
+ * do the opposite for microphone input.
+ *
+ * This driver has a generic part, described here, and a lower part
+ * that actually gets the generated byte patterns to and from the chip,
+ * which fits better in the actual phone implementation and/or in the
+ * DSP/CPU chip driver.
+ *
+ * The assumption made in this driver is that there is a single unit
+ * of this codec, and that its outputs are used as prescribed in the
+ * datasheet for headset in/out, handset in/out and handsfree in/out
+ * inasfar as these facilities are configured on the target phone in
+ * HAVE_xxx variables.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+/* Setup the sound channel to use: SOUND_NONE, _HANDSET, _HEADSET,
+ * _SPEAKER and _LINE. When set to SOUND_NONE, the chip is in setup
+ * in configuration programming mode; in any other modes, it is setup
+ * in continuous data transfer mode.
+ */
+void bottom_soundchannel (soundchannel_t sch) {
+ //TODO//
+}
+
+/* Setup the communication with the codec, initialise the chip
+ */
+void tls320aic2x_setup_sound (void) {
+ // TODO: Setup codec communications
+ bottom_soundchannel (SOUND_NONE);
+}
endchoice
+config FUNCTION_SOUND_PLAYER
+ bool "Sound player"
+ help
+ Condfigure the phone as a networked sound device. This enables
+ computers on the LAN to send sound to the phone to have it played.
+ This is only possible on an IPv6-enabled LAN.
+
config FUNCTION_DEVEL_NETCONSOLE
bool "LAN-based console"
default y if DEVEL=y
- default n if DEVEL=n
help
Direct console traffic for the main application to the network.
depends on DEVEL
select FUNCTION_DEVEL_NETCONSOLE
help
- Run a networked console. In addition to the usual console
+ This test runs a networked console. In addition to the usual console
functions, respond to any text sent by mapping it to uppercase.
The details of the networked console are described under the
add-on function for this console, one menu level up.
+config MAINFUNCTION_DEVEL_SOUND
+ bool "Test sound"
+ depends on DEVEL
+ select FUNCTION_DEVEL_SOUNDIO
+ help
+ This test turns your phone into an echo well. While off-hook, it will
+ echo handset voice to the handset with a one-second delay. If it is
+ a speakerphone, it will use the built-in microphone and speaker to do
+ the same while on-hook.
+
+ The sound is usually best implemented through DMA. Not only does this
+ save lots of interrupts while working on other things, but the reliance
+ on hardware timers and not on any software aspects helps to keep the
+ sample rate very stable, thus leading to better voice quality.
+
objs-top-$(CONFIG_MAINFUNCTION_BOOTLOADER) += src/function/bootload.o bin/top-kernel.o bin/top-simplenet.o function
+objs-top-$(CONFIG_FUNCTION_DEVEL_NETCONSOLE) += src/function/netconsole.o
+
#
# Test targets, useful during reverse engineering and forward development
#
objs-top-$(CONFIG_MAINFUNCTION_DEVEL_KEYBOARD) += src/function/develtest/keys2display.o
-objs-top-$(CONFIG_MAINFUNCTION_DEVEL_NETCONSOLE) += src/function/develtest/netconsole.o
+objs-top-$(CONFIG_MAINFUNCTION_DEVEL_NETWORK) += src/function/develtest/netconsole.o
+
+objs-top-$(CONFIG_MAINFUNCTION_DEVEL_SOUND) += src/function/develtest/echo.o
-/* netconsole.c -- run a console over LLC over ethernet */
+/* netconsole.c -- run a console over LLC over ethernet
+ *
+ * This test program includes a very small (and very selfish)
+ * network stack: It only does LLC2, and welcomes one LAN peer
+ * at a time to connect to SAP 20 where a console is running.
+ *
+ * Console log events are saved up in a buffer, and reported
+ * as soon as the client connects. From then on, new entries
+ * sent to the console are also sent immediately. An entry is
+ * made for incoming broadcast messages.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
#include <stdint.h>
#include <stdbool.h>
+#include <stdarg.h>
#include <config.h>
#include <0cpm/cpu.h>
#include <0cpm/timer.h>
#include <0cpm/led.h>
+#include <0cpm/kbd.h>
+#include <0cpm/app.h>
+#include <0cpm/show.h>
+#include <0cpm/cons.h>
+
+
+#define onlinetest_top_main top_main
+
+
+bool online = false;
+
+
+void top_timer_expiration (timing_t timeout) {
+ /* Keep the linker happy */ ;
+}
+
+void top_hook_update (bool offhook) {
+ /* Keep the linker happy */ ;
+}
+
+void top_button_press (buttonclass_t bcl, buttoncode_t cde) {
+ /* Keep the linker happy */ ;
+}
+
+void top_button_release (void) {
+ /* Keep the linker happy */ ;
+}
+
+void top_network_online (void) {
+ online = true;
+}
+
+void top_network_offline (void) {
+ online = false;
+}
+
+void top_network_can_send (void) {
+ /* Keep the linker happy */ ;
+}
+
+void top_network_can_recv (void) {
+ /* Keep the linker happy */ ;
+}
+
+
+static bool llc_connected = false;
+static uint8_t peer_sap;
+static uint8_t peer_mac [6];
+static uint8_t llc_ua [6 + 6 + 2 + 3];
+static uint8_t llc_rr [6 + 6 + 2 + 4];
+static uint8_t llc_sent;
+static uint8_t llc_received;
+
+
+/* Dummy LLC2 send routine, ignoring "cnx" as there is just one LLC2 connection.
+ * This is out-only, so N(R) always sends as 0x00 and N(S) increments.
+ * Before sending, the routine will first establish whether the last send
+ * was successful; if not, it will repeat that. The return value is true if
+ * at least the send was done, relying on future calls to resend if need be.
+ */
+uint8_t llc_pkt [100];
+uint16_t llc_pktlen;
+bool netsend_llc2 (struct llc2 *cnx, uint8_t *data, uint16_t datalen) {
+ bool newpkt;
+ if (datalen > 80) {
+ return false;
+ }
+ if (!llc_connected) {
+ return false;
+ }
+ newpkt = (llc_sent == llc_received);
+ if (newpkt) {
+ // Sending is complete, construct new packet as requested
+ memcpy (llc_pkt + 0, peer_mac, 6);
+ memcpy (llc_pkt + 6, "\x00\x0b\x82\x19\xa0\xf4", 6);
+ llc_pkt [12] = 0x00;
+ llc_pkt [13] = datalen + 4;
+ llc_pkt [14] = peer_sap; // DSAP
+ llc_pkt [15] = 20; // SSAP
+ llc_pkt [16] = llc_sent << 1; // N(S) = 0x00, information frame
+ llc_pkt [17] = 0x00; // N(R) = sent-up-to-here, low bit reset
+ memcpy (llc_pkt + 18, data, datalen);
+ llc_pktlen = 6 + 6 + 2 + 4 + datalen;
+ llc_sent++;
+ llc_sent &= 0x7f;
+ }
+ bottom_network_send (llc_pkt, llc_pktlen);
+ return newpkt;
+}
+
+struct llc2 {
+ uint8_t dummy;
+};
+static struct llc2 llc2_dummy_handle;
+
+/* LLC2-only network packet handling */
+void selfish_llc2_handler (uint8_t *pkt, uint16_t pktlen) {
+ uint16_t typelen;
+ uint16_t cmd;
+ bool ack = false;
+ typelen = (pkt [12] << 8) | pkt [13];
+#if 0
+ if ((pktlen < 14) || (typelen < 46)) {
+ bottom_printf ("Unpadded packet length %d received\n", (unsigned int) pktlen);
+ return;
+ }
+#endif
+ if (typelen > 1500) {
+ // bottom_printf ("Traffic is not LLC but protocol 0x%x\n", (unsigned int) typelen);
+ return;
+ }
+ if ((typelen > 64) && (typelen != pktlen)) {
+ bottom_printf ("Illegal length %d received (pktlen = %d)\n", (unsigned int) typelen, (unsigned int) pktlen);
+ return;
+ }
+ if (pkt [14] != 20) {
+ bottom_printf ("Received LLC traffic for SAP %d instead of 20\n", (unsigned int) pkt [14]);
+ return;
+ }
+ if (llc_connected) {
+ if (memcmp (pkt + 6, peer_mac, 6) != 0) {
+ // Peer MAC should be the constant while connected
+ return;
+ }
+ if ((pkt [15] & 0xfe) != peer_sap) {
+ // Peer SAP should be constant while connected
+ return;
+ }
+ }
+ cmd = pkt [16];
+ if (typelen > 3) {
+ cmd |= pkt [17] << 8;
+ }
+ if (cmd == 0x007f) { // SABME (llc.connect)
+ memcpy (peer_mac, pkt + 6, 6);
+ peer_sap = pkt [15] & 0xfe;
+ llc_sent = llc_received = 0x00;
+ llc_connected = true;
+ netcons_connect (&llc2_dummy_handle);
+ ack = true;
+ } else if (cmd == 0x0053) { // DISC (llc.disconnect)
+ llc_connected = false;
+ netcons_close ();
+ ack = true;
+ } else if ((cmd & 0x0001) == 0x0000) { // Data sent back (will be ignored)
+ memcpy (llc_rr + 0, peer_mac, 6);
+ memcpy (llc_rr + 6, "\x00\x0b\x82\x19\xa0\xf4", 6);
+ memcpy (llc_rr + 12, "\x00\x04\x00\x15", 4);
+ llc_rr [14] = peer_sap;
+ // memcpy (llc_rr + 12, "\x00\x04\x14\x00", 4);
+ // llc_rr [15] = peer_sap | 0x01;
+ llc_rr [16] = 0x01; // supervisory, RR
+ llc_rr [17] = (cmd + 2) & 0xfe; // outgoing N(R) = incoming N(S) + 1
+ bottom_network_send (llc_rr, sizeof (llc_rr));
+ } else if ((cmd & 0x0007) == 0x0001) { // Receiver ready / Receiver Reject
+ llc_received = (cmd >> 9);
+ } else {
+ bottom_printf ("Selfishly ignoring LLC traffic with cmd bytes 0x%x 0x%x\n", (uint32_t) pkt [16], (uint32_t) pkt [17]);
+ }
+ if (ack) {
+ memcpy (llc_ua + 0, peer_mac, 6);
+ memcpy (llc_ua + 6, "\x00\x0b\x82\x19\xa0\xf4", 6);
+ memcpy (llc_ua + 12, "\x00\x03\x00\x15\x73", 5);
+ llc_ua [14] = peer_sap;
+ // Try sending; assume repeat will be caused by other/smarter side
+ bottom_network_send (llc_ua, sizeof (llc_ua));
+ }
+}
+
+
+uint8_t netinput [1000];
+uint16_t netinputlen;
-void top_main (void) {
+void onlinetest_top_main (void) {
bottom_critical_region_end ();
+ bottom_led_set (LED_IDX_BACKLIGHT, LED_STABLE_ON);
while (true) {
- TODO: CONSOLE DRIVER OVER LLC
+ if (online) {
+ bottom_show_fixed_msg (APP_LEVEL_BACKGROUNDED, FIXMSG_READY);
+ netinputlen = sizeof (netinput);
+ if (bottom_network_recv (netinput, &netinputlen)) {
+ if (memcmp (netinput, "\xff\xff\xff\xff\xff\xff", 6) == 0) {
+ bottom_printf ("Broadcast!\n");
+#if 0
+ bottom_printf ("Broadcast from %x:%x:%x:%x:%x:%x\n",
+ (unsigned int) netinput [ 6],
+ (unsigned int) netinput [ 7],
+ (unsigned int) netinput [ 8],
+ (unsigned int) netinput [ 9],
+ (unsigned int) netinput [10],
+ (unsigned int) netinput [11]);
+#endif
+ } else {
+ bottom_led_set (LED_IDX_BACKLIGHT, LED_STABLE_OFF);
+ selfish_llc2_handler (netinput, netinputlen);
+ }
+ }
+ } else {
+ bottom_show_fixed_msg (APP_LEVEL_BACKGROUNDED, FIXMSG_OFFLINE);
+ bottom_led_set (LED_IDX_BACKLIGHT, LED_STABLE_ON);
+ }
}
}
--- /dev/null
+/* Network console interface.
+ *
+ * This interface makes it possible to connect to the phone's console
+ * over LLC. The advantage of LLC is that it works on a LAN only,
+ * and does not require any support of protocols higher than Ethernet.
+ * This makes it suitable to even debug things like IP leases.
+ *
+ * The common interface to these routines can be found in <0cpm/cons.h>
+ * and defines a user-level routine bottom_printf (fmt, ...) which is
+ * a restricted printf() function. It is mapped to the implementation
+ * of bottom_console_printf() below.
+ *
+ * The network side of this facility uses netcons_connect() and
+ * netcons_close() to notify the console of a LAN host opening and
+ * closing the connection. The console will send data out using the
+ * commonly available network sending routines.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <config.h>
+#include <0cpm/cons.h>
+
+
+/*
+ * The entire data and code portion depends on the function NETCONSOLE
+ * being configured through "make menuconfig".
+ */
+#ifdef CONFIG_FUNCTION_DEVEL_NETCONSOLE
+
+
+/******** BUFFER STATIC VARIABLES ********/
+
+// TODO: Configuration option would be nice for CONSBUFLEN (max 64k - 1)
+#define CONSBUFLEN 1024
+
+static char consbuf [CONSBUFLEN];
+
+static uint16_t rpos = CONSBUFLEN, wpos = 0;
+
+
+/******** NETWORK INTERFACE ROUTINES ********/
+
+static struct llc2 *netcons_connection = NULL;
+
+static void trysend (void) {
+ if (netcons_connection) {
+ if (rpos > wpos) {
+ while (rpos < CONSBUFLEN) {
+ uint16_t blksz = CONSBUFLEN - rpos;
+ if (blksz > 80) {
+ blksz = 80;
+ }
+ if (netsend_llc2 (netcons_connection, consbuf + rpos, blksz)) {
+ rpos += blksz;
+ } else {
+ return;
+ }
+ }
+ rpos = 0;
+ }
+ while (rpos < wpos) {
+ uint16_t blksz = wpos - rpos;
+ if (blksz > 80) {
+ blksz = 80;
+ }
+ if (netsend_llc2 (netcons_connection, consbuf + rpos, blksz)) {
+ rpos += blksz;
+ } else {
+ return;
+ }
+ }
+ }
+}
+
+void netcons_connect (struct llc2 *cnx) {
+ netcons_connection = cnx;
+ trysend ();
+}
+
+void netcons_close (void) {
+ netcons_connection = NULL;
+}
+
+
+/******** PRINTING ROUTINES ********/
+
+static const char digits [] = "0123456789abcdef";
+
+static void cons_putchar (char c) {
+ // TODO: Handle buffer full conditions
+ if (wpos >= CONSBUFLEN) {
+ wpos = 0;
+ }
+ consbuf [wpos++] = c;
+}
+
+static void cons_putint (uint32_t val, uint8_t base, uint8_t minpos) {
+ uint32_t divisor = 1;
+ while (minpos-- > 0) {
+ divisor *= base;
+ }
+ while (val / base > divisor) {
+ divisor *= base;
+ }
+ while (divisor > 0) {
+ cons_putchar (digits [(val / divisor)]);
+ val %= divisor;
+ divisor /= base;
+ }
+}
+
+void bottom_console_vprintf (char *fmt, va_list argh) {
+ char *fp = fmt;
+ char *str;
+ char ch;
+ uint32_t intval;
+ while (*fp) {
+ if (*fp != '%') {
+ cons_putchar (*fp++);
+ } else {
+ fp++;
+ switch (*fp++) {
+ case '\0':
+ fp--;
+ break;
+ case 'c':
+ ch = va_arg (argh, char);
+ cons_putchar (ch);
+ break;
+ case 's':
+ str = va_arg (argh, char *);
+ while (*str) {
+ cons_putchar (*str++);
+ }
+ break;
+ case 'd':
+ intval = (uint32_t) va_arg (argh, unsigned int);
+ cons_putint (intval, 10, 0);
+ break;
+ case 'l':
+ intval = va_arg (argh, uint32_t);
+ cons_putint (intval, 10, 0);
+ break;
+ case 'p':
+ intval = (uint32_t) va_arg (argh, void *);
+ cons_putint (intval, 16, 8);
+ break;
+ case 'x':
+ intval = (uint32_t) va_arg (argh, unsigned int);
+ cons_putint (intval, 16, 8);
+ break;
+ case '%':
+ default:
+ cons_putchar (fp [-1]);
+ break;
+ }
+ }
+ }
+ trysend ();
+}
+
+void bottom_console_printf (char *fmt, ...) {
+ va_list argh;
+ va_start (argh, fmt);
+ bottom_console_vprintf (fmt, argh);
+ va_end (argh);
+}
+
+
+#endif /* CONFIG_FUNCTION_DEVEL_NETCONSOLE */