diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5680696 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.elf +*.img + diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..41944de --- /dev/null +++ b/config.txt @@ -0,0 +1,53 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] +kernel_old=1 +disable_commandline_tags=1 +core_freq_min=500 +hdmi_group=1 +hdmi_mode=16 diff --git a/part10-multicore/Makefile b/part10-multicore/Makefile index 485538a..9fe0560 100644 --- a/part10-multicore/Makefile +++ b/part10-multicore/Makefile @@ -1,22 +1,22 @@ CFILES = $(wildcard *.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin all: clean kernel8.img boot.o: boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c boot.S -o boot.o + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o audio.o : audio.bin - $(LLVMPATH)/llvm-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ + $(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot.o $(OFILES) audio.o - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot.o $(OFILES) audio.o -T link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) audio.o -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean: /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part10-multicore/Makefile.gcc b/part10-multicore/Makefile.gcc deleted file mode 100644 index 9fe0560..0000000 --- a/part10-multicore/Makefile.gcc +++ /dev/null @@ -1,22 +0,0 @@ -CFILES = $(wildcard *.c) -OFILES = $(CFILES:.c=.o) -GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles -GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin - -all: clean kernel8.img - -boot.o: boot.S - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o - -audio.o : audio.bin - $(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ - -%.o: %.c - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ - -kernel8.img: boot.o $(OFILES) audio.o - $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) audio.o -T link.ld -o kernel8.elf - $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img - -clean: - /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part10-multicore/io.c b/part10-multicore/io.c index a2a888d..b9ba71f 100644 --- a/part10-multicore/io.c +++ b/part10-multicore/io.c @@ -19,8 +19,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part11-breakout-smp/Makefile b/part11-breakout-smp/Makefile index a53ba5c..9c0526f 100644 --- a/part11-breakout-smp/Makefile +++ b/part11-breakout-smp/Makefile @@ -1,25 +1,25 @@ CFILES = $(wildcard *.c lib/*.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin + all: clean kernel8.img boot/boot.o: boot/boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot/boot.S -o boot/boot.o bin/BCM4345C0.o : bin/BCM4345C0.hcd - $(LLVMPATH)/llvm-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ - + $(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ + bin/audio.o : bin/audio.bin - $(LLVMPATH)/llvm-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ + $(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot/boot.o $(OFILES) bin/BCM4345C0.o bin/audio.o - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot/boot.o $(OFILES) bin/BCM4345C0.o bin/audio.o -T boot/link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img - + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot/boot.o $(OFILES) bin/BCM4345C0.o bin/audio.o -T boot/link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean: - /bin/rm kernel8.elf *.o bin/*.o boot/*.o lib/*.o *.img > /dev/null 2> /dev/null || true + /bin/rm kernel8.elf *.o bin/*.o boot/*.o *.img > /dev/null 2> /dev/null || true diff --git a/part11-breakout-smp/lib/io.c b/part11-breakout-smp/lib/io.c index 34a02aa..77e23eb 100644 --- a/part11-breakout-smp/lib/io.c +++ b/part11-breakout-smp/lib/io.c @@ -19,8 +19,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part12-wgt/lib/io.c b/part12-wgt/lib/io.c index 34a02aa..77e23eb 100644 --- a/part12-wgt/lib/io.c +++ b/part12-wgt/lib/io.c @@ -19,8 +19,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part13-interrupts/Makefile b/part13-interrupts/Makefile index 84c8539..fa40d98 100644 --- a/part13-interrupts/Makefile +++ b/part13-interrupts/Makefile @@ -1,20 +1,20 @@ CFILES = $(wildcard *.c lib/*.c kernel/*.c) SFILES = $(wildcard boot/*.S lib/*.S kernel/*.S) OFILES = $(CFILES:.c=.o) $(SFILES:.S=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin all: clean kernel8.img %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ %.o: %.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib $(OFILES) -T boot/link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib $(OFILES) -T boot/link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean: /bin/rm kernel8.elf */*.o *.img > /dev/null 2> /dev/null || true diff --git a/part13-interrupts/lib/io.c b/part13-interrupts/lib/io.c index 34a02aa..77e23eb 100644 --- a/part13-interrupts/lib/io.c +++ b/part13-interrupts/lib/io.c @@ -19,8 +19,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part13-interrupts/Makefile.gcc b/part13a-gpio-interrupts/Makefile similarity index 100% rename from part13-interrupts/Makefile.gcc rename to part13a-gpio-interrupts/Makefile diff --git a/part13a-gpio-interrupts/README.md b/part13a-gpio-interrupts/README.md new file mode 100644 index 0000000..4814ce3 --- /dev/null +++ b/part13a-gpio-interrupts/README.md @@ -0,0 +1,164 @@ +Writing a "bare metal" operating system for Raspberry Pi 4 (Part 13) +==================================================================== + +[< Go back to part12-wgt](../part12-wgt) + +What are interrupts? +-------------------- +If you've spent any time looking at the Bluetooth code in these tutorials, you'll notice we're always "polling" for updates. In fact, in _part11-breakout-smp_ we tie up an entire core just waiting around for something to happen. This clearly isn't the best use of CPU time. Fortunately, the world solved that problem for us years ago with _interrupts_. + +Ideally, we want to tell a piece of hardware to do something and have it simply notify us when the work is complete so we can move on with our lives in the meantime. These notifications are known as _interrupts_ because they disrupt normal program execution and force the CPU to immediately run an _interrupt handler_. + +The simplest device that interrupts +----------------------------------- +One useful piece of built-in hardware is a system timer, which can be programmed to interrupt at regular intervals e.g. every second. You'll need this if you want to schedule multiple processes to run on a single core e.g. using the principle of time slicing. + +For now, however, we're simply going to learn how to program the timer and respond to its interrupts. + +The codebase +------------ +Let me quickly explain what you're looking at in the _part13-interrupts_ code: + + * _boot/_ : the same _boot_ code directory from _part12-wgt_ + * _include/_ : some useful headers copied directly from _part11-multicore_ + * _lib/_ : some useful libraries copied directly from _part11-multicore_ + * _kernel/_ : the only new code we need to concern ourselves with in this tutorial + +Please note: I have also done some work to tidy up the _Makefile_ and respect this directory structure, but nothing to write home about! + +The new code +------------ +You'll recognise a lot of _kernel.c_ from _part10-multicore_, except instead of showing four cores at work and playing sound, we're now only using core 0 & 1 and, in addition, making use of two timer interrupts to show four progress bars. So, the `main()` routine kicks off core 1, sets up the timers, and then finally kicks off core 0's workload. + +The timers are set up using these calls: + +```c +irq_init_vectors(); +enable_interrupt_controller(); +irq_barrier(); +irq_enable(); +timer_init(); +``` + +Initialising the exception vector table +--------------------------------------- +In fact, interrupts are a more specific kind of _exception_ - something that, when "raised", needs the immediate attention of the processor. A perfect example of when an exception might occur is when bad code tries to do something "impossible" e.g. divide by zero. The CPU needs to know how to respond when/if this happens i.e. jump to an address of some code to run which handles this exception gracefully e.g. by printing an error to the screen. These addresses are stored in an _exception vector table_. + +_irqentry.S_ sets up a list called `vectors` which contains individual _vector entries_. These vector entries are simply jump instructions to handler code. + +The CPU is told where this exception vector table is stored during the `irq_init_vectors()` call from `main()` in _kernel.c_. You'll find this code in _utils.S_: + +```c +irq_init_vectors: + adr x0, vectors + msr vbar_el1, x0 + ret +``` + +It simply sets the Vector Base Address Register to the address of the `vectors` list. + +Interrupt handling +------------------ +The only vector entry we really care about for the purposes of this tutorial is `handle_el1_irq`. This is a generic handler for any interrupt request (IRQ) that comes in at EL1 (kernel execution level). + +If you do want a deeper understanding, I highly recommend reading s-matyukevich's work [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson03/rpi-os.md). + +```c +handle_el1_irq: + kernel_entry + bl handle_irq + kernel_exit +``` + +Put simply, `kernel_entry` saves the register state before the interrupt handler runs, and `kernel_exit` restores this register state before we return. As we're _interrupting_ normal program execution, we want to be sure that we put things back to how they were so that nothing unpredictable happens as our kernel code resumes. + +In the middle we simply call a function called `handle_irq()` which is written in the C language in _irq.c_. Its purpose is to look more closely at the interrupt request, figure out what device was responsible for generating an interrupt, and run the right sub-handler: + +```c +void handle_irq() { + unsigned int irq = REGS_IRQ->irq0_pending_0; + + while(irq & (SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3)) { + if (irq & SYS_TIMER_IRQ_1) { + irq &= ~SYS_TIMER_IRQ_1; + + handle_timer_1(); + } + + if (irq & SYS_TIMER_IRQ_3) { + irq &= ~SYS_TIMER_IRQ_3; + + handle_timer_3(); + } + } +} +``` + +As you can see, we're handling two different timer interrupts in this code. In fact, `handle_timer_1()` and `handle_timer_3()` are implemented in _kernel.c_ and serve to demonstrate that the timer has fired by incrementing a progress counter and updating a graphical representation of its value. Timer 3 is configured to progress at 4 times the speed of Timer 1. + +The interrupt controller +------------------------ +The interrupt controller is the hardware responsible for telling the CPU about interrupts as they occur. We can use the interrupt controller to act as a gatekeeper and allow/block (or enable/disable) interrupts. We can also use it to figure out which device generated the interrupt, as we did in `handle_irq()`. + +In `enable_interrupt_controller()`, called from `main()` in _kernel.c_, we allow the Timer 1 and Timer 3 interrupts through and in `disable_interrupt_controller()` we block all interrupts: + +```c +void enable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3; +} + +void disable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = 0; +} +``` + +Masking/unmasking interrupts +---------------------------- +To begin receiving interrupts, we need to take one more step: unmasking all types of interrupts. + +Masking is a technique used by the CPU to prevent a particular piece of code from being stopped in its tracks by an interrupt. It's used to protect important code that *must* complete. Imagine what would happen if our `kernel_entry` code (that saves register state) was interrupted halfway through! In this case, the register state would be overwritten and lost. This is why the CPU automatically masks all interrupts when an exception handler is executed. + +The `irq_enable` and `irq_disable` functions in _utils.S_ are responsible for masking and unmasking interrupts. + +They are helped by the `irq_barrier` function which ensures that the `enable_interrupt_controller()` call properly finishes before the `irq_enable()` call is made: + +```c +.globl irq_enable +irq_enable: + msr daifclr, #2 + ret + +.globl irq_disable +irq_disable: + msr daifset, #2 + ret + +.globl irq_barrier +irq_barrier: + dsb sy + ret +``` + +As soon as `irq_enable()` is called from `main()` in _kernel.c_, the timer handler is run when the timer interrupt fires. Well, sort of...! + +Initialising the system timer +----------------------------- +We still need to initialise the timer. + +The RPi4's system timer couldn't be simpler. It has a counter which increases by 1 with each clock tick. It then has 4 interrupt lines (0 & 2 reserved for the GPU, 1 & 3 used by us in this tutorial!) with 4 corresponding compare registers. When the value of the counter becomes equal to a value in one of the compare registers, the corresponding interrupt is fired. + +So before we receive any timer interrupts, we must also set the right compare registers to have a non-zero value. The `timer_init()` function (called from `main()` in _kernel.c_) gets the current timer value, adds the timer interval and sets the compare register to that total, so when the right number of clock ticks pass, the interrupt fires. It does this for both Timer 1 and Timer 3, setting Timer 3 to run 4 times as fast. + +Handling the timer interrupts +----------------------------- +This is the simplest bit. + +We update the compare register so the next interrupt will be generated after the same interval again. Importantly we then acknowledge the interrupt by setting the right bit of the Control Status register. + +Then we update the screen to show our progress! + +_And... hey presto! You're handling two system timer interrupts like a pro!_ + +![Timers firing on all cylinders on the Raspberry Pi 4](images/13-interrupts-running.jpg) + +[Go to part14-spi-ethernet >](../part14-spi-ethernet) diff --git a/part13a-gpio-interrupts/boot/boot.S b/part13a-gpio-interrupts/boot/boot.S new file mode 100644 index 0000000..f644aee --- /dev/null +++ b/part13a-gpio-interrupts/boot/boot.S @@ -0,0 +1,114 @@ +#include "sysregs.h" + +#define LOCAL_CONTROL 0xff800000 +#define LOCAL_PRESCALER 0xff800008 +#define OSC_FREQ 54000000 +#define MAIN_STACK 0x400000 + +.section ".text.boot" // Make sure the linker puts this at the start of the kernel image + +.global _start // Execution starts here + +_start: + ldr x0, =LOCAL_CONTROL // Sort out the timer + str wzr, [x0] + mov w1, 0x80000000 + str w1, [x0, #(LOCAL_PRESCALER - LOCAL_CONTROL)] + + ldr x0, =OSC_FREQ + msr cntfrq_el0, x0 + msr cntvoff_el2, xzr + + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + + // We're not on the main core, so hang in an infinite wait loop + adr x5, spin_cpu0 +1: wfe + ldr x4, [x5, x1, lsl #3] + cbz x4, 1b + + ldr x2, =__stack_start // Get ourselves a fresh stack - location depends on CPU core asking + lsl x1, x1, #9 // Multiply core_number by 512 + add x3, x2, x1 // Add to the address + mov sp, x3 + + mov x0, #0 + mov x1, #0 + mov x2, #0 + mov x3, #0 + br x4 + b 1b +2: // We're on the main core! + // First enable the FPU + + mov x0, #0x33ff + msr cptr_el3, x0 // Disable coprocessor traps to EL3 + mov x0, #3 << 20 + msr cpacr_el1, x0 // Enable FP/SIMD at EL1 + + // Now get ready to switch from EL3 down to EL1 + + ldr x0, =SCTLR_VALUE_MMU_DISABLED + msr sctlr_el1, x0 + + ldr x0, =HCR_VALUE + msr hcr_el2, x0 + + ldr x0, =SCR_VALUE + msr scr_el3, x0 + + ldr x0, =SPSR_VALUE + msr spsr_el3, x0 + + adr x0, el1_entry + msr elr_el3, x0 + + eret +el1_entry: + // We're in EL1 + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Set stack to start somewhere safe + mov sp, #MAIN_STACK + + // Jump to our main() routine in C (make sure it doesn't return) +4: bl main + // In case it does return, halt the master core too + b 1b + +.ltorg + +.org 0x110 +.globl spin_cpu0 +spin_cpu0: + .quad 0 + +.org 0x118 +.globl spin_cpu1 +spin_cpu1: + .quad 0 + +.org 0x120 +.globl spin_cpu2 +spin_cpu2: + .quad 0 + +.org 0x128 +.globl spin_cpu3 +spin_cpu3: + .quad 0 + +.globl get_el +get_el: + mrs x0, CurrentEL + lsr x0, x0, #2 + ret diff --git a/part13a-gpio-interrupts/boot/link.ld b/part13a-gpio-interrupts/boot/link.ld new file mode 100644 index 0000000..4e84390 --- /dev/null +++ b/part13a-gpio-interrupts/boot/link.ld @@ -0,0 +1,35 @@ +SECTIONS +{ + .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } + PROVIDE(_data = .); + .data : { *(.data .data.* .gnu.linkonce.d*) } + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + __bss_size = (__bss_end - __bss_start)>>3; + } + .cpu1Stack : + { + . = ALIGN(16); + __stack_start = .; + . = . + 512; + __cpu1_stack = .; + } + .cpu2Stack : + { + . = . + 512; + __cpu2_stack = .; + } + .cpu3Stack : + { + . = . + 512; + __cpu3_stack = .; + } + _end = .; + + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} diff --git a/part13a-gpio-interrupts/boot/sysregs.h b/part13a-gpio-interrupts/boot/sysregs.h new file mode 100644 index 0000000..385cd79 --- /dev/null +++ b/part13a-gpio-interrupts/boot/sysregs.h @@ -0,0 +1,44 @@ +#ifndef _SYSREGS_H +#define _SYSREGS_H + +// *************************************** +// SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. +// *************************************** + +#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) +#define SCTLR_EE_LITTLE_ENDIAN (0 << 25) +#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) +#define SCTLR_I_CACHE_DISABLED (0 << 12) +#define SCTLR_D_CACHE_DISABLED (0 << 2) +#define SCTLR_I_CACHE_ENABLED (1 << 12) +#define SCTLR_D_CACHE_ENABLED (1 << 2) +#define SCTLR_MMU_DISABLED (0 << 0) +#define SCTLR_MMU_ENABLED (1 << 0) + +#define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_ENABLED | SCTLR_D_CACHE_ENABLED | SCTLR_MMU_DISABLED) + +// *************************************** +// HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. +// *************************************** + +#define HCR_RW (1 << 31) +#define HCR_VALUE HCR_RW + +// *************************************** +// SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. +// *************************************** + +#define SCR_RESERVED (3 << 4) +#define SCR_RW (1 << 10) +#define SCR_NS (1 << 0) +#define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) + +// *************************************** +// SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. +// *************************************** + +#define SPSR_MASK_ALL (7 << 6) +#define SPSR_EL1h (5 << 0) +#define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) + +#endif diff --git a/part13a-gpio-interrupts/images/13-interrupts-running.jpg b/part13a-gpio-interrupts/images/13-interrupts-running.jpg new file mode 100755 index 0000000..d92edba Binary files /dev/null and b/part13a-gpio-interrupts/images/13-interrupts-running.jpg differ diff --git a/part13a-gpio-interrupts/include/fb.h b/part13a-gpio-interrupts/include/fb.h new file mode 100644 index 0000000..8beda43 --- /dev/null +++ b/part13a-gpio-interrupts/include/fb.h @@ -0,0 +1,13 @@ +void fb_init(); +void drawPixel(int x, int y, unsigned char attr); +void drawChar(unsigned char ch, int x, int y, unsigned char attr, int zoom); +void drawString(int x, int y, char *s, unsigned char attr, int zoom); +void drawRect(int x1, int y1, int x2, int y2, unsigned char attr, int fill); +void drawCircle(int x0, int y0, int radius, unsigned char attr, int fill); +void drawLine(int x1, int y1, int x2, int y2, unsigned char attr); +void moveRect(int oldx, int oldy, int width, int height, int shiftx, int shifty, unsigned char attr); +void wait_msec(unsigned int n); +void debugstr(char *str); +void debugcrlf(void); +void debugch(unsigned char b); +void debughex(unsigned int d); diff --git a/part13a-gpio-interrupts/include/io.h b/part13a-gpio-interrupts/include/io.h new file mode 100644 index 0000000..ff2fa98 --- /dev/null +++ b/part13a-gpio-interrupts/include/io.h @@ -0,0 +1,17 @@ +#define PERIPHERAL_BASE 0xFE000000 +#define LEGACY_BASE 0x7E000000 +#define SAFE_ADDRESS 0x00400000 // Somewhere safe to store a lot of data + +void uart_init(); +void uart_writeText(char *buffer); +void uart_loadOutputFifo(); +unsigned char uart_readByte(); +unsigned int uart_isReadByteReady(); +void uart_writeByteBlockingActual(unsigned char ch); +void uart_update(); +void mmio_write(long reg, unsigned int val); +unsigned int mmio_read(long reg); +void gpio_useAsAlt0(unsigned int pin_number); +void gpio_useAsAlt3(unsigned int pin_number); +void uart_hex(unsigned int d); +void uart_byte(unsigned char b); diff --git a/part13a-gpio-interrupts/include/mb.h b/part13a-gpio-interrupts/include/mb.h new file mode 100644 index 0000000..0e1861b --- /dev/null +++ b/part13a-gpio-interrupts/include/mb.h @@ -0,0 +1,34 @@ +extern volatile unsigned int mbox[36]; + +enum { + MBOX_REQUEST = 0 +}; + +enum { + MBOX_CH_POWER = 0, + MBOX_CH_FB = 1, + MBOX_CH_VUART = 2, + MBOX_CH_VCHIQ = 3, + MBOX_CH_LEDS = 4, + MBOX_CH_BTNS = 5, + MBOX_CH_TOUCH = 6, + MBOX_CH_COUNT = 7, + MBOX_CH_PROP = 8 // Request from ARM for response by VideoCore +}; + +enum { + MBOX_TAG_SETPOWER = 0x28001, + MBOX_TAG_SETCLKRATE = 0x38002, + + MBOX_TAG_SETPHYWH = 0x48003, + MBOX_TAG_SETVIRTWH = 0x48004, + MBOX_TAG_SETVIRTOFF = 0x48009, + MBOX_TAG_SETDEPTH = 0x48005, + MBOX_TAG_SETPXLORDR = 0x48006, + MBOX_TAG_GETFB = 0x40001, + MBOX_TAG_GETPITCH = 0x40008, + + MBOX_TAG_LAST = 0 +}; + +unsigned int mbox_call(unsigned char ch); diff --git a/part13a-gpio-interrupts/include/multicore.h b/part13a-gpio-interrupts/include/multicore.h new file mode 100644 index 0000000..4c3dc0f --- /dev/null +++ b/part13a-gpio-interrupts/include/multicore.h @@ -0,0 +1,11 @@ +extern unsigned int spin_cpu0; +extern unsigned int spin_cpu1; +extern unsigned int spin_cpu2; +extern unsigned int spin_cpu3; + +void start_core1(void (*func)(void)); +void start_core2(void (*func)(void)); +void start_core3(void (*func)(void)); +void clear_core1(void); +void clear_core2(void); +void clear_core3(void); diff --git a/part13a-gpio-interrupts/include/terminal.h b/part13a-gpio-interrupts/include/terminal.h new file mode 100644 index 0000000..ef05b85 --- /dev/null +++ b/part13a-gpio-interrupts/include/terminal.h @@ -0,0 +1,253 @@ +unsigned int vgapal[] = { + 0x000000, + 0x0000AA, + 0x00AA00, + 0x00AAAA, + 0xAA0000, + 0xAA00AA, + 0xAA5500, + 0xAAAAAA, + 0x555555, + 0x5555FF, + 0x55FF55, + 0x55FFFF, + 0xFF5555, + 0xFF55FF, + 0xFFFF55, + 0xFFFFFF +}; + +enum { + FONT_WIDTH = 8, + FONT_HEIGHT = 8, + FONT_BPG = 8, // Bytes per glyph + FONT_BPL = 1, // Bytes per line + FONT_NUMGLYPHS = 224 +}; + +unsigned char font[FONT_NUMGLYPHS][FONT_BPG] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) + { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) + { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") + { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) + { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) + { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) + { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) + { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') + { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() + { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) + { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) + { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) + { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) + { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) + { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) + { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) + { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) + { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) + { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) + { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) + { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) + { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) + { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) + { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) + { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) + { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) + { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) + { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) + { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) + { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) + { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) + { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) + { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) + { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) + { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) + { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) + { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) + { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) + { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) + { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) + { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) + { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) + { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) + { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) + { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) + { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) + { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) + { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) + { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) + { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) + { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) + { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) + { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) + { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) + { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) + { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) + { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) + { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) + { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) + { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) + { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) + { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) + { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) + { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) + { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) + { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) + { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) + { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) + { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) + { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) + { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) + { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) + { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) + { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) + { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) + { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) + { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) + { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) + { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) + { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) + { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F + { 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x18, 0x30, 0x1E}, // U+00C7 (C cedille) + { 0x00, 0x33, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FC (u umlaut) + { 0x38, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00E9 (e aigu) + { 0x7E, 0xC3, 0x3C, 0x60, 0x7C, 0x66, 0xFC, 0x00}, // U+00E2 (a circumflex) + { 0x33, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E4 (a umlaut) + { 0x07, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E0 (a grave) + { 0x0C, 0x0C, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E5 (a ring) + { 0x00, 0x00, 0x1E, 0x03, 0x03, 0x1E, 0x30, 0x1C}, // U+00E7 (c cedille) + { 0x7E, 0xC3, 0x3C, 0x66, 0x7E, 0x06, 0x3C, 0x00}, // U+00EA (e circumflex) + { 0x33, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00EB (e umlaut) + { 0x07, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00E8 (e grave) + { 0x33, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00EF (i umlaut) + { 0x3E, 0x63, 0x1C, 0x18, 0x18, 0x18, 0x3C, 0x00}, // U+00EE (i circumflex) + { 0x07, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00EC (i grave) + { 0x63, 0x1C, 0x36, 0x63, 0x7F, 0x63, 0x63, 0x00}, // U+00C4 (A umlaut) + { 0x0C, 0x0C, 0x00, 0x1E, 0x33, 0x3F, 0x33, 0x00}, // U+00C5 (A ring) + { 0x07, 0x00, 0x3F, 0x06, 0x1E, 0x06, 0x3F, 0x00}, // U+00C8 (E grave) + { 0x00, 0x00, 0xFE, 0x30, 0xFE, 0x33, 0xFE, 0x00}, // U+00E6 (ae) + { 0x7C, 0x36, 0x33, 0x7F, 0x33, 0x33, 0x73, 0x00}, // U+00C6 (AE) + { 0x1E, 0x33, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F4 (o circumflex) + { 0x00, 0x33, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F6 (o umlaut) + { 0x00, 0x07, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F2 (o grave) + { 0x1E, 0x33, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FB (u circumflex) + { 0x00, 0x07, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00F9 (u grave) + { 0x00, 0x33, 0x00, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+00FF (y umlaut) + { 0xC3, 0x18, 0x3C, 0x66, 0x66, 0x3C, 0x18, 0x00}, // U+00D6 (O umlaut) + { 0x33, 0x00, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+00DC (U umlaut) + { 0x18, 0x18, 0x7E, 0x03, 0x03, 0x7E, 0x18, 0x18}, // U+00A2 (dollarcents) + { 0x1C, 0x36, 0x26, 0x0F, 0x06, 0x67, 0x3F, 0x00}, // U+00A3 (pound sterling) + { 0x33, 0x33, 0x1E, 0x3F, 0x0C, 0x3F, 0x0C, 0x0C}, // U+00A5 (yen) + { 0x7C, 0xC6, 0x1C, 0x36, 0x36, 0x1C, 0x33, 0x1E}, // U+00A7 (paragraph) + { 0x70, 0xD8, 0x18, 0x3C, 0x18, 0x18, 0x1B, 0x0E}, // U+0192 (dutch florijn) + { 0x38, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E1 (a aigu) + { 0x1C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00ED (i augu) + { 0x00, 0x38, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F3 (o aigu) + { 0x00, 0x38, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FA (u aigu) + { 0x00, 0x1F, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x00}, // U+00F1 (n ~) + { 0x3F, 0x00, 0x33, 0x37, 0x3F, 0x3B, 0x33, 0x00}, // U+00D1 (N ~) + { 0x3C, 0x36, 0x36, 0x7C, 0x00, 0x00, 0x00, 0x00}, // U+00AA (superscript a) + { 0x1C, 0x36, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00}, // U+00BA (superscript 0) + { 0x0C, 0x00, 0x0C, 0x06, 0x03, 0x33, 0x1E, 0x00}, // U+00BF (inverted ?) + { 0x00, 0x00, 0x00, 0x3F, 0x03, 0x03, 0x00, 0x00}, // U+2310 (gun pointing right) + { 0x00, 0x00, 0x00, 0x3F, 0x30, 0x30, 0x00, 0x00}, // U+00AC (gun pointing left) + { 0xC3, 0x63, 0x33, 0x7B, 0xCC, 0x66, 0x33, 0xF0}, // U+00BD (1/2) + { 0xC3, 0x63, 0x33, 0xBD, 0xEC, 0xF6, 0xF3, 0x03}, // U+00BC (1/4) + { 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00}, // U+00A1 (inverted !) + { 0x00, 0xCC, 0x66, 0x33, 0x66, 0xCC, 0x00, 0x00}, // U+00AB (<<) + { 0x00, 0x33, 0x66, 0xCC, 0x66, 0x33, 0x00, 0x00}, // U+00BB (>>) + { 0x55, 0x00, 0xAA, 0x00, 0x55, 0x00, 0xAA, 0x00}, // U+2591 (25% solid) + { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA}, // U+2592 (50% solid) + { 0xFF, 0xAA, 0xFF, 0x55, 0xFF, 0xAA, 0xFF, 0x55}, // U+2593 (75% solid) + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}, // U+2502 (thin vertical) + { 0x08, 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, 0x08}, // U+2524 (down L, left L, up L) + { 0x08, 0x08, 0x08, 0x0F, 0x08, 0x0F, 0x08, 0x08}, // U+2561 (up L, down L, left D) + { 0x14, 0x14, 0x14, 0x14, 0x17, 0x14, 0x14, 0x14}, // U+2562 (up D, down D, left L) + { 0x00, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x14, 0x14}, // U+2556 (down D, left L) + { 0x00, 0x00, 0x00, 0x0F, 0x08, 0x0F, 0x08, 0x08}, // U+2555 (down L, left D) + { 0x14, 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14}, // U+2563 (up D, down D, left D) + { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14}, // U+2551 (double vertical) + { 0x00, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x14, 0x14}, // U+2557 (down D, left D) + { 0x14, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x00, 0x00}, // U+255D (up D, left D) + { 0x14, 0x14, 0x14, 0x14, 0x1F, 0x00, 0x00, 0x00}, // U+255C (up D, left L) + { 0x08, 0x08, 0x08, 0x0F, 0x08, 0x0F, 0x00, 0x00}, // U+255B (up L, left D) + { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, 0x08}, // U+2510 (down L, left L) + { 0x08, 0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, 0x00}, // U+2514 (up L, right L) + { 0x08, 0x08, 0x08, 0x08, 0xff, 0x00, 0x00, 0x00}, // U+2534 (up L, right L, left L) + { 0x00, 0x00, 0x00, 0x00, 0xff, 0x08, 0x08, 0x08}, // U+252C (down L, right L, left L) + { 0x08, 0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, 0x08}, // U+251C (down L, right L, up L) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00}, // U+2500 (thin horizontal) + { 0x08, 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, 0x08}, // U+253C (up L, right L, left L, down L) + { 0x08, 0x08, 0x08, 0xF8, 0x08, 0xF8, 0x08, 0x08}, // U+255E (up L, down L, right D) + { 0x14, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x14, 0x14}, // U+255F (up D, down D, right L) + { 0x14, 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x00, 0x00}, // U+255A (up D, right D) + { 0x00, 0x00, 0x00, 0xFC, 0x04, 0xF4, 0x14, 0x14}, // U+2554 (down D, right D) + { 0x14, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00}, // U+2569 (left D, right D, up D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14}, // U+2566 (left D, right D, down D) + { 0x14, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x14, 0x14}, // U+2560 (up D, down D, right D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00}, // U+2550 (double horizontal) + { 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14}, // U+256C (left D, right D, down D, up D) + { 0x08, 0x08, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0x00}, // U+2567 (left D, right D, up L) + { 0x14, 0x14, 0x14, 0x14, 0xFF, 0x00, 0x00, 0x00}, // U+2568 (left L, right L, up D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x08}, // U+2564 (left D, right D, down L) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x14, 0x14, 0x14}, // U+2565 (left L, right L, down D) + { 0x14, 0x14, 0x14, 0x14, 0xFC, 0x00, 0x00, 0x00}, // U+2559 (up D, right L) + { 0x08, 0x08, 0x08, 0xF8, 0x08, 0xF8, 0x00, 0x00}, // U+2558 (up L, right D) + { 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF8, 0x08, 0x08}, // U+2552 (down L, right D) + { 0x00, 0x00, 0x00, 0x00, 0xFC, 0x14, 0x14, 0x14}, // U+2553 (down D, right L) + { 0x14, 0x14, 0x14, 0x14, 0xFF, 0x14, 0x14, 0x14}, // U+256B (left L, right L, down D, up D) + { 0x08, 0x08, 0x08, 0xFF, 0x08, 0xFF, 0x08, 0x08}, // U+256A (left D, right D, down L, up L) + { 0x08, 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, 0x00}, // U+2518 (up L, left L) + { 0x00, 0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08}, // U+250C (down L, right L) + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // U+2588 (solid) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, // U+2584 (bottom half) + { 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}, // U+258C (left half) + { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0}, // U+2590 (right half) + { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, // U+2580 (top half) +}; diff --git a/part13a-gpio-interrupts/kernel/irq.c b/part13a-gpio-interrupts/kernel/irq.c new file mode 100644 index 0000000..6cccb90 --- /dev/null +++ b/part13a-gpio-interrupts/kernel/irq.c @@ -0,0 +1,60 @@ +#include "kernel.h" + +// textdefinitionmap of invalid error-types +const char entry_error_messages[16][32] = { + "SYNC_INVALID_EL1t", + "IRQ_INVALID_EL1t", + "FIQ_INVALID_EL1t", + "ERROR_INVALID_EL1T", + + "SYNC_INVALID_EL1h", + "IRQ_INVALID_EL1h", + "FIQ_INVALID_EL1h", + "ERROR_INVALID_EL1h", + + "SYNC_INVALID_EL0_64", + "IRQ_INVALID_EL0_64", + "FIQ_INVALID_EL0_64", + "ERROR_INVALID_EL0_64", + + "SYNC_INVALID_EL0_32", + "IRQ_INVALID_EL0_32", + "FIQ_INVALID_EL0_32", + "ERROR_INVALID_EL0_32" +}; + +void show_invalid_entry_message(unsigned int type, unsigned long esr, unsigned long address) { + debugstr("ERROR CAUGHT: %s - %d, ESR: %X, Address: %X\n", + entry_error_messages[type], type, esr, address); +} + +void enable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3 | SPI_INT; +} + +void disable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = 0; +} + +void handle_irq() { + unsigned int irq = REGS_IRQ->irq0_pending_0; + + while(irq & (SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3 | SPI_INT)) { + switch(irq) { + case (SYS_TIMER_IRQ_1): + irq &= ~SYS_TIMER_IRQ_1; + handle_timer_1(); + break; + case (SYS_TIMER_IRQ_3): + irq &= ~SYS_TIMER_IRQ_3; + handle_timer_3(); + break; + case(SPI_INT): + irq &= ~SPI_INT; + handle_spi(); + break; + default: + debugstr("Unknown pending irq: %x /r/n",irq); + } + } +} diff --git a/part13a-gpio-interrupts/kernel/irqentry.S b/part13a-gpio-interrupts/kernel/irqentry.S new file mode 100644 index 0000000..7aea7a0 --- /dev/null +++ b/part13a-gpio-interrupts/kernel/irqentry.S @@ -0,0 +1,156 @@ +//D1.10.2 of ARMv8 reference manual +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +//stack frame size +#define S_FRAME_SIZE 256 + +.macro kernel_entry + sub sp, sp, #S_FRAME_SIZE + stp x0, x1, [sp, #16 * 0] + stp x2, x3, [sp, #16 * 1] + stp x4, x5, [sp, #16 * 2] + stp x6, x7, [sp, #16 * 3] + stp x8, x9, [sp, #16 * 4] + stp x10, x11, [sp, #16 * 5] + stp x12, x13, [sp, #16 * 6] + stp x14, x15, [sp, #16 * 7] + stp x16, x17, [sp, #16 * 8] + stp x18, x19, [sp, #16 * 9] + stp x20, x21, [sp, #16 * 10] + stp x22, x23, [sp, #16 * 11] + stp x24, x25, [sp, #16 * 12] + stp x26, x27, [sp, #16 * 13] + stp x28, x29, [sp, #16 * 14] + str x30, [sp, #16 * 15] +.endm + +.macro kernel_exit + ldp x0, x1, [sp, #16 * 0] + ldp x2, x3, [sp, #16 * 1] + ldp x4, x5, [sp, #16 * 2] + ldp x6, x7, [sp, #16 * 3] + ldp x8, x9, [sp, #16 * 4] + ldp x10, x11, [sp, #16 * 5] + ldp x12, x13, [sp, #16 * 6] + ldp x14, x15, [sp, #16 * 7] + ldp x16, x17, [sp, #16 * 8] + ldp x18, x19, [sp, #16 * 9] + ldp x20, x21, [sp, #16 * 10] + ldp x22, x23, [sp, #16 * 11] + ldp x24, x25, [sp, #16 * 12] + ldp x26, x27, [sp, #16 * 13] + ldp x28, x29, [sp, #16 * 14] + ldr x30, [sp, #16 * 15] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.macro handle_invalid_entry type + kernel_entry + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + bl show_invalid_entry_message // We could pass this to a function to print an error here + b err_hang +.endm + +.macro ventry label +.align 7 + b \label +.endm + +//Exception vectors table +.align 11 +.globl vectors +vectors: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry handle_el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 + ventry irq_invalid_el0_64 // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + + +sync_invalid_el1t: + handle_invalid_entry SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry SYNC_INVALID_EL1h + +fiq_invalid_el1h: + handle_invalid_entry FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry ERROR_INVALID_EL1h + +sync_invalid_el0_64: + handle_invalid_entry SYNC_INVALID_EL0_64 + +irq_invalid_el0_64: + handle_invalid_entry IRQ_INVALID_EL0_64 + +fiq_invalid_el0_64: + handle_invalid_entry FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry ERROR_INVALID_EL0_32 + +handle_el1_irq: + kernel_entry + bl handle_irq + kernel_exit + +.globl err_hang +err_hang: b err_hang diff --git a/part13a-gpio-interrupts/kernel/kernel.c b/part13a-gpio-interrupts/kernel/kernel.c new file mode 100644 index 0000000..2a2d62c --- /dev/null +++ b/part13a-gpio-interrupts/kernel/kernel.c @@ -0,0 +1,152 @@ +#include "../include/fb.h" +#include "../include/io.h" +#include "../include/multicore.h" +#include "kernel.h" + +void initProgress(void) +{ + drawRect(0, 0, 301, 50, 0x0f, 0); + drawString(309, 21, "Core 0", 0x0f, 1); + + drawRect(0, 60, 301, 110, 0x0f, 0); + drawString(309, 81, "Core 1", 0x0f, 1); + + drawRect(0, 120, 301, 170, 0x0f, 0); + drawString(309, 141, "Timer 1", 0x0f, 1); + + drawRect(0, 180, 301, 230, 0x0f, 0); + drawString(309, 201, "Timer 3", 0x0f, 1); +} + +void drawProgress(unsigned int core, unsigned int val) { + unsigned char col = (core + 1) + ((core + 1) << 4); + + // val should be 0-100 + if (val == 0) drawRect(1, (60 * core) + 1, 300, (60 * core) + 49, 0x00, 1); + if (val > 0) drawRect(1, (60 * core) + 1, (val * 3), (60 * core) + 49, col, 1); +} + +void core0_main(void) +{ + unsigned int core0_val = 0; + + while (core0_val <= 100) { + wait_msec(0x100000); + drawProgress(0, core0_val); + core0_val++; + } + + debugstr("Core 0 done."); + debugcrlf(); + + irq_disable(); + disable_interrupt_controller(); + + while(1); +} + +void core1_main(void) +{ + unsigned int core1_val = 0; + + clear_core1(); // Only run once + + while (core1_val <= 100) { + wait_msec(0x3FFFF); + drawProgress(1, core1_val); + core1_val++; + } + + debugstr("Core 1 done."); + debugcrlf(); + + while(1); +} + +/* +// ISR for incoming packet + +debugstr("we are into isr now"); +// Re-enable Interrupts +ENC_IRQHandler(&handle); + + + +*/ +// TIMER FUNCTIONS + +const unsigned int timer1_int = CLOCKHZ; +const unsigned int timer3_int = CLOCKHZ / 4; +unsigned int timer1_val = 0; +unsigned int timer3_val = 0; + +void timer_init() { + timer1_val = REGS_TIMER->counter_lo; + timer1_val += timer1_int; + REGS_TIMER->compare[1] = timer1_val; + + timer3_val = REGS_TIMER->counter_lo; + timer3_val += timer3_int; + REGS_TIMER->compare[3] = timer3_val; +} + +void handle_spi() { + //while((REGS_AUX->mu_iir & 4) == 4) { + debugstr("UART Recv: "); + //uart_send(uart_recv()); + debugstr("\n"); + //} + debugstr("button pressed"); +} + +void handle_timer_1() { + timer1_val += timer1_int; + REGS_TIMER->compare[1] = timer1_val; + REGS_TIMER->control_status |= SYS_TIMER_IRQ_1; + + unsigned int progval = timer1_val / timer1_int; + if (progval <= 100) { + drawProgress(2, progval); + } else { + debugstr("Timer 1 done."); + debugcrlf(); + } +} + +void handle_timer_3() { + timer3_val += timer3_int; + REGS_TIMER->compare[3] = timer3_val; + REGS_TIMER->control_status |= SYS_TIMER_IRQ_3; + + unsigned int progval = timer3_val / timer3_int; + if (progval <= 100) drawProgress(3, progval); +} + +void main(void) +{ + fb_init(); + + // Init GPIO interupt + //init_GPIOinterrupt(); + + unsigned int i=0; + while (i++<30) debugcrlf(); + + initProgress(); + + // Kick it off on core 1 + + start_core1(core1_main); + + // Kick off the timers + + irq_init_vectors(); + enable_interrupt_controller(); + irq_barrier(); + irq_enable(); + timer_init(); + + // Loop endlessly + + core0_main(); +} diff --git a/part13a-gpio-interrupts/kernel/kernel.h b/part13a-gpio-interrupts/kernel/kernel.h new file mode 100644 index 0000000..b8d21c8 --- /dev/null +++ b/part13a-gpio-interrupts/kernel/kernel.h @@ -0,0 +1,54 @@ +#define PERIPHERAL_BASE 0xFE000000 +#define CLOCKHZ 1000000 + +struct timer_regs { + volatile unsigned int control_status; + volatile unsigned int counter_lo; + volatile unsigned int counter_hi; + volatile unsigned int compare[4]; +}; + +#define REGS_TIMER ((struct timer_regs *)(PERIPHERAL_BASE + 0x00003000)) + +struct arm_irq_regs_2711 { + volatile unsigned int irq0_pending_0; + volatile unsigned int irq0_pending_1; + volatile unsigned int irq0_pending_2; + volatile unsigned int res0; + volatile unsigned int irq0_enable_0; + volatile unsigned int irq0_enable_1; + volatile unsigned int irq0_enable_2; + volatile unsigned int res1; + volatile unsigned int irq0_disable_0; + volatile unsigned int irq0_disable_1; + volatile unsigned int irq0_disable_2; +}; + +typedef struct arm_irq_regs_2711 arm_irq_regs; + +#define REGS_IRQ ((arm_irq_regs *)(PERIPHERAL_BASE + 0x0000B200)) + +enum vc_irqs { + SYS_TIMER_IRQ_0 = 1, + SYS_TIMER_IRQ_1 = 2, + SYS_TIMER_IRQ_2 = 4, + SYS_TIMER_IRQ_3 = 8, + AUX_IRQ = (1 << 29), + GPIO_INT_0 = (1 << 49), + GPIO_INT_1 = (1 << 50), + GPIO_INT_2 = (1 << 51), + GPIO_INT_3 = (1 << 52), + SPI_INT = (1 << 54) +}; + +// from util.S +void irq_init_vectors(); +void irq_enable(); +void irq_barrier(); +void irq_disable(); +void enable_interrupt_controller(); +void disable_interrupt_controller(); + +void handle_timer_1(); +void handle_timer_3(); +void handle_spi(); diff --git a/part13a-gpio-interrupts/kernel/utils.S b/part13a-gpio-interrupts/kernel/utils.S new file mode 100644 index 0000000..47637ab --- /dev/null +++ b/part13a-gpio-interrupts/kernel/utils.S @@ -0,0 +1,21 @@ +//D13.2.137 of ARMv8 reference manual +.globl irq_init_vectors +irq_init_vectors: + adr x0, vectors + msr vbar_el1, x0 + ret +//C5.2.2 of ARMv8 reference manual +.globl irq_enable +irq_enable: + msr daifclr, #2 + ret + +.globl irq_disable +irq_disable: + msr daifset, #2 + ret + +.globl irq_barrier +irq_barrier: + dsb sy + ret diff --git a/part13a-gpio-interrupts/lib/fb.c b/part13a-gpio-interrupts/lib/fb.c new file mode 100644 index 0000000..2cc89f7 --- /dev/null +++ b/part13a-gpio-interrupts/lib/fb.c @@ -0,0 +1,260 @@ +#include "../include/io.h" +#include "../include/mb.h" +#include "../include/terminal.h" + +unsigned int width, height, pitch, isrgb; +unsigned char *fb; + +int curx = 0; +int cury = 0; + +void fb_init() +{ + mbox[0] = 35*4; // Length of message in bytes + mbox[1] = MBOX_REQUEST; + + mbox[2] = MBOX_TAG_SETPHYWH; // Tag identifier + mbox[3] = 8; // Value size in bytes + mbox[4] = 0; + mbox[5] = 1920; // Value(width) + mbox[6] = 1080; // Value(height) + + mbox[7] = MBOX_TAG_SETVIRTWH; + mbox[8] = 8; + mbox[9] = 8; + mbox[10] = 1920; + mbox[11] = 1080; + + mbox[12] = MBOX_TAG_SETVIRTOFF; + mbox[13] = 8; + mbox[14] = 8; + mbox[15] = 0; // Value(x) + mbox[16] = 0; // Value(y) + + mbox[17] = MBOX_TAG_SETDEPTH; + mbox[18] = 4; + mbox[19] = 4; + mbox[20] = 32; // Bits per pixel + + mbox[21] = MBOX_TAG_SETPXLORDR; + mbox[22] = 4; + mbox[23] = 4; + mbox[24] = 1; // RGB + + mbox[25] = MBOX_TAG_GETFB; + mbox[26] = 8; + mbox[27] = 8; + mbox[28] = 4096; // FrameBufferInfo.pointer + mbox[29] = 0; // FrameBufferInfo.size + + mbox[30] = MBOX_TAG_GETPITCH; + mbox[31] = 4; + mbox[32] = 4; + mbox[33] = 0; // Bytes per line + + mbox[34] = MBOX_TAG_LAST; + + // Check call is successful and we have a pointer with depth 32 + if (mbox_call(MBOX_CH_PROP) && mbox[20] == 32 && mbox[28] != 0) { + mbox[28] &= 0x3FFFFFFF; // Convert GPU address to ARM address + width = mbox[10]; // Actual physical width + height = mbox[11]; // Actual physical height + pitch = mbox[33]; // Number of bytes per line + isrgb = mbox[24]; // Pixel order + fb = (unsigned char *)((long)mbox[28]); + } +} + +void drawPixel(int x, int y, unsigned char attr) +{ + int offs = (y * pitch) + (x * 4); + *((unsigned int*)(fb + offs)) = vgapal[attr & 0x0f]; +} + +void drawRect(int x1, int y1, int x2, int y2, unsigned char attr, int fill) +{ + int y=y1; + + while (y <= y2) { + int x=x1; + while (x <= x2) { + if ((x == x1 || x == x2) || (y == y1 || y == y2)) drawPixel(x, y, attr); + else if (fill) drawPixel(x, y, (attr & 0xf0) >> 4); + x++; + } + y++; + } +} + +void drawLine(int x1, int y1, int x2, int y2, unsigned char attr) +{ + int dx, dy, p, x, y; + + dx = x2-x1; + dy = y2-y1; + x = x1; + y = y1; + p = 2*dy-dx; + + while (x= 0) { + drawPixel(x,y,attr); + y++; + p = p+2*dy-2*dx; + } else { + drawPixel(x,y,attr); + p = p+2*dy; + } + x++; + } +} + +void drawCircle(int x0, int y0, int radius, unsigned char attr, int fill) +{ + int x = radius; + int y = 0; + int err = 0; + + while (x >= y) { + if (fill) { + drawLine(x0 - y, y0 + x, x0 + y, y0 + x, (attr & 0xf0) >> 4); + drawLine(x0 - x, y0 + y, x0 + x, y0 + y, (attr & 0xf0) >> 4); + drawLine(x0 - x, y0 - y, x0 + x, y0 - y, (attr & 0xf0) >> 4); + drawLine(x0 - y, y0 - x, x0 + y, y0 - x, (attr & 0xf0) >> 4); + } + drawPixel(x0 - y, y0 + x, attr); + drawPixel(x0 + y, y0 + x, attr); + drawPixel(x0 - x, y0 + y, attr); + drawPixel(x0 + x, y0 + y, attr); + drawPixel(x0 - x, y0 - y, attr); + drawPixel(x0 + x, y0 - y, attr); + drawPixel(x0 - y, y0 - x, attr); + drawPixel(x0 + y, y0 - x, attr); + + if (err <= 0) { + y += 1; + err += 2*y + 1; + } + + if (err > 0) { + x -= 1; + err -= 2*x + 1; + } + } +} + +void drawChar(unsigned char ch, int x, int y, unsigned char attr, int zoom) +{ + unsigned char *glyph = (unsigned char *)&font + (ch < FONT_NUMGLYPHS ? ch : 0) * FONT_BPG; + + for (int i=1;i<=(FONT_HEIGHT*zoom);i++) { + for (int j=0;j<(FONT_WIDTH*zoom);j++) { + unsigned char mask = 1 << (j/zoom); + unsigned char col = (*glyph & mask) ? attr & 0x0f : (attr & 0xf0) >> 4; + + drawPixel(x+j, y+i, col); + } + glyph += (i%zoom) ? 0 : FONT_BPL; + } +} + +void drawString(int x, int y, char *s, unsigned char attr, int zoom) +{ + while (*s) { + if (*s == '\r') { + x = 0; + } else if(*s == '\n') { + x = 0; y += (FONT_HEIGHT*zoom); + } else { + drawChar(*s, x, y, attr, zoom); + x += (FONT_WIDTH*zoom); + } + s++; + } +} + +void moveRect(int oldx, int oldy, int width, int height, int shiftx, int shifty, unsigned char attr) +{ + unsigned int newx = oldx + shiftx, newy = oldy + shifty; + unsigned int xcount = 0, ycount = 0; + unsigned int bitmap[width][height]; // This is very unsafe if it's too big for the stack... + unsigned int offs; + + // Save the bitmap + while (xcount < width) { + while (ycount < height) { + offs = ((oldy + ycount) * pitch) + ((oldx + xcount) * 4); + + bitmap[xcount][ycount] = *((unsigned int*)(fb + offs)); + ycount++; + } + ycount=0; + xcount++; + } + // Wipe it out with background colour + drawRect(oldx, oldy, oldx + width, oldy + width, attr, 1); + // Draw it again + for (int i=newx;i= 1920) { + curx = 0; cury += 8; + } + if (cury + 8 >= 1080) { + cury = 0; + } + drawString(curx, cury, str, 0x0f, 1); + curx += (strlen(str) * 8); +} + +void debugcrlf(void) { + curx = 0; cury += 8; +} + +void debugch(unsigned char b) { + unsigned int n; + int c; + for(c=4;c>=0;c-=4) { + n=(b>>c)&0xF; + n+=n>9?0x37:0x30; + debugstr((char *)&n); + } + debugstr(" "); +} + +void debughex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + n=(d>>c)&0xF; + n+=n>9?0x37:0x30; + debugstr((char *)&n); + } + debugstr(" "); +} diff --git a/part13a-gpio-interrupts/lib/io.c b/part13a-gpio-interrupts/lib/io.c new file mode 100644 index 0000000..34a02aa --- /dev/null +++ b/part13a-gpio-interrupts/lib/io.c @@ -0,0 +1,199 @@ +#include "../include/io.h" + +// GPIO + +enum { + GPFSEL0 = PERIPHERAL_BASE + 0x200000, + GPSET0 = PERIPHERAL_BASE + 0x20001C, + GPCLR0 = PERIPHERAL_BASE + 0x200028, + GPPUPPDN0 = PERIPHERAL_BASE + 0x2000E4 +}; + +enum { + GPIO_MAX_PIN = 53, + GPIO_FUNCTION_OUT = 1, + GPIO_FUNCTION_ALT5 = 2, + GPIO_FUNCTION_ALT3 = 7, + GPIO_FUNCTION_ALT0 = 4 +}; + +enum { + Pull_None = 0, + Pull_Down = 1, // Are down and up the right way around? + Pull_Up = 2 +}; + +void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } +unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; } + +unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) { + unsigned int field_mask = (1 << field_size) - 1; + + if (pin_number > field_max) return 0; + if (value > field_mask) return 0; + + unsigned int num_fields = 32 / field_size; + unsigned int reg = base + ((pin_number / num_fields) * 4); + unsigned int shift = (pin_number % num_fields) * field_size; + + unsigned int curval = mmio_read(reg); + curval &= ~(field_mask << shift); + curval |= value << shift; + mmio_write(reg, curval); + + return 1; +} + +unsigned int gpio_set (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); } +unsigned int gpio_clear (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); } +unsigned int gpio_pull (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); } +unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); } + +void gpio_useAsAlt0(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT0); +} + +void gpio_useAsAlt3(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT3); +} + +void gpio_useAsAlt5(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT5); +} + +void gpio_initOutputPinWithPullNone(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_OUT); +} + +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff) { + if (onOrOff) { + gpio_set(pin_number, 1); + } else { + gpio_clear(pin_number, 1); + } +} + +// UART + +enum { + AUX_BASE = PERIPHERAL_BASE + 0x215000, + AUX_IRQ = AUX_BASE, + AUX_ENABLES = AUX_BASE + 4, + AUX_MU_IO_REG = AUX_BASE + 64, + AUX_MU_IER_REG = AUX_BASE + 68, + AUX_MU_IIR_REG = AUX_BASE + 72, + AUX_MU_LCR_REG = AUX_BASE + 76, + AUX_MU_MCR_REG = AUX_BASE + 80, + AUX_MU_LSR_REG = AUX_BASE + 84, + AUX_MU_MSR_REG = AUX_BASE + 88, + AUX_MU_SCRATCH = AUX_BASE + 92, + AUX_MU_CNTL_REG = AUX_BASE + 96, + AUX_MU_STAT_REG = AUX_BASE + 100, + AUX_MU_BAUD_REG = AUX_BASE + 104, + AUX_UART_CLOCK = 500000000, + UART_MAX_QUEUE = 16 * 1024 +}; + +#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1) + +unsigned char uart_output_queue[UART_MAX_QUEUE]; +unsigned int uart_output_queue_write = 0; +unsigned int uart_output_queue_read = 0; + +void uart_init() { + mmio_write(AUX_ENABLES, 1); //enable UART1 + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_CNTL_REG, 0); + mmio_write(AUX_MU_LCR_REG, 3); //8 bits + mmio_write(AUX_MU_MCR_REG, 0); + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts + mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200)); + gpio_useAsAlt5(14); + gpio_useAsAlt5(15); + mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX +} + +unsigned int uart_isOutputQueueEmpty() { + return uart_output_queue_read == uart_output_queue_write; +} + +unsigned int uart_isReadByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x01; } +unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; } + +unsigned char uart_readByte() { + while (!uart_isReadByteReady()); + return (unsigned char)mmio_read(AUX_MU_IO_REG); +} + +void uart_writeByteBlockingActual(unsigned char ch) { + while (!uart_isWriteByteReady()); + mmio_write(AUX_MU_IO_REG, (unsigned int)ch); +} + +void uart_loadOutputFifo() { + while (!uart_isOutputQueueEmpty() && uart_isWriteByteReady()) { + uart_writeByteBlockingActual(uart_output_queue[uart_output_queue_read]); + uart_output_queue_read = (uart_output_queue_read + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + } +} + +void uart_writeByteBlocking(unsigned char ch) { + unsigned int next = (uart_output_queue_write + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + + while (next == uart_output_queue_read) uart_loadOutputFifo(); + + uart_output_queue[uart_output_queue_write] = ch; + uart_output_queue_write = next; +} + +void uart_writeText(char *buffer) { + while (*buffer) { + if (*buffer == '\n') uart_writeByteBlockingActual('\r'); + uart_writeByteBlockingActual(*buffer++); + } +} + +void uart_drainOutputQueue() { + while (!uart_isOutputQueueEmpty()) uart_loadOutputFifo(); +} + +void uart_update() { + uart_loadOutputFifo(); + + if (uart_isReadByteReady()) { + unsigned char ch = uart_readByte(); + if (ch == '\r') uart_writeText("\n"); else uart_writeByteBlocking(ch); + } +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + // get highest tetrad + n=(d>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlockingActual(n); + } +} + +void uart_byte(unsigned char b) { + unsigned int n; + int c; + for(c=4;c>=0;c-=4) { + // get highest tetrad + n=(b>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlockingActual(n); + } + uart_writeByteBlockingActual(' '); +} diff --git a/part13a-gpio-interrupts/lib/mb.c b/part13a-gpio-interrupts/lib/mb.c new file mode 100644 index 0000000..65dbbb8 --- /dev/null +++ b/part13a-gpio-interrupts/lib/mb.c @@ -0,0 +1,39 @@ +#include "../include/io.h" + +// The buffer must be 16-byte aligned as only the upper 28 bits of the address can be passed via the mailbox +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +enum { + VIDEOCORE_MBOX = (PERIPHERAL_BASE + 0x0000B880), + MBOX_READ = (VIDEOCORE_MBOX + 0x0), + MBOX_POLL = (VIDEOCORE_MBOX + 0x10), + MBOX_SENDER = (VIDEOCORE_MBOX + 0x14), + MBOX_STATUS = (VIDEOCORE_MBOX + 0x18), + MBOX_CONFIG = (VIDEOCORE_MBOX + 0x1C), + MBOX_WRITE = (VIDEOCORE_MBOX + 0x20), + MBOX_RESPONSE = 0x80000000, + MBOX_FULL = 0x80000000, + MBOX_EMPTY = 0x40000000 +}; + +unsigned int mbox_call(unsigned char ch) +{ + // 28-bit address (MSB) and 4-bit value (LSB) + unsigned int r = ((unsigned int)((long) &mbox) &~ 0xF) | (ch & 0xF); + + // Wait until we can write + while (mmio_read(MBOX_STATUS) & MBOX_FULL); + + // Write the address of our buffer to the mailbox with the channel appended + mmio_write(MBOX_WRITE, r); + + while (1) { + // Is there a reply? + while (mmio_read(MBOX_STATUS) & MBOX_EMPTY); + + // Is it a reply to our message? + if (r == mmio_read(MBOX_READ)) return mbox[1]==MBOX_RESPONSE; // Is it successful? + + } + return 0; +} diff --git a/part13a-gpio-interrupts/lib/multicore.c b/part13a-gpio-interrupts/lib/multicore.c new file mode 100644 index 0000000..0000bcd --- /dev/null +++ b/part13a-gpio-interrupts/lib/multicore.c @@ -0,0 +1,44 @@ +#include "../include/multicore.h" + +void store32(unsigned long address, unsigned long value) +{ + *(unsigned long *) address = value; +} + +unsigned long load32(unsigned long address) +{ + return *(unsigned long *) address; +} + +void start_core1(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu1, (unsigned long)func); + asm volatile ("sev"); +} + +void start_core2(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu2, (unsigned long)func); + asm volatile ("sev"); +} + +void start_core3(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu3, (unsigned long)func); + asm volatile ("sev"); +} + +void clear_core1(void) +{ + store32((unsigned long)&spin_cpu1, 0); +} + +void clear_core2(void) +{ + store32((unsigned long)&spin_cpu2, 0); +} + +void clear_core3(void) +{ + store32((unsigned long)&spin_cpu3, 0); +} diff --git a/part15-tcpip-webserver/Makefile.gcc b/part14-spi-ethernet-interrupt/Makefile similarity index 79% rename from part15-tcpip-webserver/Makefile.gcc rename to part14-spi-ethernet-interrupt/Makefile index cb4b843..e208c92 100644 --- a/part15-tcpip-webserver/Makefile.gcc +++ b/part14-spi-ethernet-interrupt/Makefile @@ -1,8 +1,9 @@ -CFILES = $(wildcard *.c lib/*.c kernel/*.c net/*.c tcpip/*.c) +CFILES = $(wildcard *.c lib/*.c kernel/*.c net/*.c) SFILES = $(wildcard boot/*.S lib/*.S kernel/*.S) OFILES = $(CFILES:.c=.o) $(SFILES:.S=.o) GCCFLAGS = -Wall -O0 -ffreestanding -nostdlib -nostartfiles GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin +BOOTMNT ?= /media/cvdeveloper/bootfs all: clean kernel8.img @@ -15,6 +16,10 @@ all: clean kernel8.img kernel8.img: $(OFILES) $(GCCPATH)/aarch64-none-elf-ld -nostdlib $(OFILES) -T boot/link.ld -o kernel8.elf $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img + + cp kernel8.img $(BOOTMNT)/kernel8.img + cp config.txt $(BOOTMNT)/ + sync clean: /bin/rm kernel8.elf */*.o *.img > /dev/null 2> /dev/null || true diff --git a/part14-spi-ethernet-interrupt/README.md b/part14-spi-ethernet-interrupt/README.md new file mode 100644 index 0000000..9597ea9 --- /dev/null +++ b/part14-spi-ethernet-interrupt/README.md @@ -0,0 +1,211 @@ +Writing a "bare metal" operating system for Raspberry Pi 4 (Part 14) +==================================================================== + +[< Go back to part13-interrupts](../part13-interrupts) + +Bare metal Ethernet for under £10 +--------------------------------- +It's exciting to build your own OS, but until you give it the ability to communicate with the outside world, your possibilities are limited. Indeed, our simple Bluetooth comms got us up and running - but if we're to do anything meaningful then we need proper networking. + +In this tutorial, we're going to connect to an external Ethernet controller (a network card, if you like) using the RPi4's Serial Peripheral Interface (SPI). + +Things you'll need: + + * An [ENC28J60 Ethernet module](https://www.amazon.co.uk/dp/B00DB76ZSK) - it cost me less than £6 and was worth every penny (n.b. code only tested on this exact model) + * Some [female-to-female jumper cables](https://www.amazon.co.uk/dp/B072LN3HLG) - cost me less than £2.50 + * An Ethernet cable to connect to your Internet router + +Connecting up the ENC28J60 Ethernet module +------------------------------------------ +I followed the very helpful instructions [here](https://www.instructables.com/Super-Cheap-Ethernet-for-the-Raspberry-Pi/) to hook up the ENC28J60 to the RPi4's SPI0 interface. + +We won't be connecting the interrupt line for now, so there are just six jumper leads (I've suggested colours) that need connecting: + + | Pi pin | Pi GPIO | Jumper colour | ENC28J60 pin | + | ------ | ----------- | ------------- | ------------ | + | Pin 17 | +3V3 power | Red | VCC | + | Pin 19 | GPIO10/MOSI | Green | SI | + | Pin 20 | GND | Black | GND | + | Pin 21 | GPIO09/MISO | Yellow | SO | + | Pin 23 | GPIO11/SCLK | Blue | SCK | + | Pin 24 | GPIO08/CE0 | Green | CS | + +![GPIO location](../part3-helloworld/images/3-helloworld-pinloc.png) + +Here's a (not very useful) photo of my RPi4 connected correctly: + +![ENC28J60 connections](images/14-spi-ethernet-photo.jpg) + +The SPI library +--------------- +Let's start by looking at how we implement SPI communication. + +I'm not going to write a long paper on how SPI works and why we need it, because it's [very well documented elsewhere](https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/). It's recommended background reading, but not essential if all you want to do is get something working. + +Look at _lib/spi.c_. It uses some of existing functions in _lib/io.c_ that you'll remember from earlier tutorials. In fact, I've added two functions to the _include/io.h_ header file so we can call them from our SPI library: + +```c +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff); +void gpio_initOutputPinWithPullNone(unsigned int pin_number); +``` + +Specifically, `spi_init()` sets GPIO 7, 9, 10, and 11 to use the ALT0 function. Cross-referencing with the [BCM2711 ARM Peripherals document](https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf), page 77, you'll see that this maps SPI0 to the GPIO header. GPIO 8 is mapped as an output pin, since we'll use this to signal to the ENC28J60 that we want to talk. In fact, the `spi_chip_select()` function takes a true/false (boolean) parameter which either sets or clears this pin. + +Looking at the SPI0 register map on page 136, we see this reflected in our `REGS_SPI0` structure. This gives us handy access to the SPI0 peripheral's memory-mapped registers. + +Our `spi_send_recv()` function then sets us up for some communcation: + + * Sets the DLEN Register to the number of bytes to transfer (a length we passed into the function) + * Clears the RX & TX FIFOs + * Sets the Transfer Active (TA) flag + +Then, whilst there's either data to write or data to read (and we haven't written/read more bytes than we asked for), we write to/read from the FIFO using the buffers we passed in. Once we think we're done, we wait until the SPI interface agrees i.e. the DONE flag in the CS Register is set. If there are extraneous bytes to read, we just throw them away (well, dump them to the screen for now because this shouldn't happen). + +Finally, to be absolutely sure, we clear the TA flag. + +I've then set up two convenient functions - `spi_send()` and `spi_recv()` - which exercise `spi_send_recv()`, mainly to make future code more readable. + +The ENC28J60 drivers +-------------------- +Let's now look into the _net/_ subdirectory. + +Both _enc28j60.c_ and _enc28j60.h_ make up the driver code for the ENC28J60 Ethernet module. Whilst we could have laboured for months writing our own driver based on [the module's datasheet](http://ww1.microchip.com/downloads/en/devicedoc/39662c.pdf), I chose to leverage somebody else's hard work instead. It felt like a win that I could effortlessly bring somebody else's good code into my own OS! I did, however, make sure I understood what the code was doing at every turn (optional!). + +Thanks to [this Github repository](https://github.com/wolfgangr/enc28j60) for saving me months of work. I made a very few changes to the code, but nothing worth documenting here. If you're keen to see how little I needed to change, clone the repo and make good use of the `diff` command. + +What I did need to do is write some bridging code between the driver and the RPi4 hardware. Essentially, I'm talking about hooking up our SPI library to the driver - the whole reason for _encspi.c_. + +It defines four functions that the driver requires (well documented in the _enc28j60.h_ file): + +```c +void ENC_SPI_Select(unsigned char truefalse) { + spi_chip_select(!truefalse); // If it's true, select 0 (the ENC), if false, select 1 (i.e. deselect the ENC) +} + +void ENC_SPI_SendBuf(unsigned char *master2slave, unsigned char *slave2master, unsigned short bufferSize) { + spi_chip_select(0); + spi_send_recv(master2slave, slave2master, bufferSize); + spi_chip_select(1); // De-select the ENC +} + +void ENC_SPI_Send(unsigned char command) { + spi_chip_select(0); + spi_send(&command, 1); + spi_chip_select(1); // De-select the ENC +} + +void ENC_SPI_SendWithoutSelection(unsigned char command) { + spi_send(&command, 1); +} +``` + +Perhaps the most confusing aspect is the chip selection. Through a bit of trial & error I discovered that when GPIO08 is clear, the device is selected, and when it's set, the device is deselected. It turns out this is because the Chip Select pin on the ENC28J60 is active LOW and there's no invertor on the board (presumably to save costs), so GPIO08 must be LOW to enable the IC. + +Some more timer functions +------------------------- +The only other thing our ENC28J60 driver requires is access to a couple of well-defined timer functions: + + * `HAL_GetTick()` - returns the current number of timer ticks since start + * `HAL_Delay()` - delays by a specified number of milliseconds + +These are quickly implemented in _kernel/kernel.c_ and weren't too much of a stretch after _part13-interrupts_: + +```c +unsigned long HAL_GetTick(void) { + unsigned int hi = REGS_TIMER->counter_hi; + unsigned int lo = REGS_TIMER->counter_lo; + + //double check hi value didn't change after setting it... + if (hi != REGS_TIMER->counter_hi) { + hi = REGS_TIMER->counter_hi; + lo = REGS_TIMER->counter_lo; + } + + return ((unsigned long)hi << 32) | lo; +} + +void HAL_Delay(unsigned int ms) { + unsigned long start = HAL_GetTick(); + + while(HAL_GetTick() < start + (ms * 1000)); +} +``` + +Let's connect! +-------------- +So we have a working driver that's interfacing with our hardware via _net/encspi.c_ and a few timer functions in _kernel/kernel.c_. Now what? + +The design goals of our kernel's networking demo will be to: + + 1. Prove we can talk to the hardware + 2. Bring the network up successfully + 3. Prove we can connect to something else on the network and get a response + +My proposals for how we fulfil these goals are: + + 1. Prove we can detect whether a network link has been established at a physical level (CAT5 cable plugged in and connected to a working switch) + 2. Rely on the ENC28J60 driver to tell us that we've started up successfully + 3. Handcraft and send an [ARP](https://en.wikipedia.org/wiki/Address_Resolution_Protocol) request and await an ARP response from my Internet router (the traditional way devices "find each other" on a network from a point of zero knowledge) + +Look at _kernel/arp.c_. First we create a handle to reference our driver instance `ENC_HandleTypeDef handle`. We then initialise this structure in `init_network()`: + +```c +handle.Init.DuplexMode = ETH_MODE_HALFDUPLEX; +handle.Init.MACAddr = myMAC; +handle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; +handle.Init.InterruptEnableBits = EIE_LINKIE | EIE_PKTIE; +``` + +This starts the module in half duplex mode (can't transmit & receive simultaneously), sets its MAC address (my favourite: `C0:FF:EE:C0:FF:EE`), tells the hardware to add its own packet checksums (we don't want to have to create them in software), and enables interrupt messages for "link up/down" and "packet received". + +We then call the driver routine `ENC_Start(&handle)` and check it returns true (this fulfils design requirement 2 - the driver tells us we've started correctly). We go on to set the MAC address using `ENC_SetMacAddr(&handle)`. + +This line waits until a physical network link has been established (fulfilling design requirement 1): + +```c +while (!(handle.LinkStatus & PHSTAT2_LSTAT)) ENC_IRQHandler(&handle); +``` + +The driver's `ENC_IRQHandler(&handle)` routine would ordinarily be called when an interrupt was raised to refresh the driver status flags. Because we didn't hook up the interrupt line and to keep things simple, we're just polling in the software for now. When we see the `handle.LinkStatus` flag has the `PHSTAT2_LSTAT` bit set, we know the link is up (documented on page 24 of the module's datasheet). + +Before we're done, we have to re-enable Ethernet interrupts (`ENC_IRQHandler()` disables them, but doesn't re-enable them - something I discovered by reading the code). + +Sending/receiving an ARP +------------------------ +To transmit on an Ethernet network, we need to format our packets correctly. The ENC28J60 deals with the physical layer (including the checksum, as we asked it to), so we only need concern ourselves with the data link layer - made up of a header, and a payload. + +The header (our `EtherNetII` struct) is simply a destination and source MAC address, as well as a [16-bit packet type](https://en.wikipedia.org/wiki/EtherType). ARP packets, for example, have type `0x0806`. You'll note in our `#define ARPPACKET` that we've swapped the two bytes. This is because big-endianness is the dominant ordering in network protocols, and the RPi4 is a little-endian architecture (some reading may be required here!). We've had to do this across the board. + +The payload is the full [ARP packet](https://en.wikipedia.org/wiki/Address_Resolution_Protocol) defined in the `ARP` struct. The `SendArpPacket()` function sets up the data we need in the structure (documented in code comments) and then uses driver calls to transmit the packet: + +```c +// Send the packet + +if (ENC_RestoreTXBuffer(&handle, sizeof(ARP)) == 0) { + debugstr("Sending ARP request."); + debugcrlf(); + + ENC_WriteBuffer((unsigned char *)&arpPacket, sizeof(ARP)); + handle.transmitLength = sizeof(ARP); + + ENC_Transmit(&handle); +} +``` + +`ENC_RestoreTXBuffer()` simply prepares the transmit buffer and returns 0 if successful. `ENC_WriteBuffer()` sends the packet to the ENC28J60 over the SPI. We then set the transmit buffer length in the driver status flags and call `ENC_Transmit()` to tell the ENC to send the packet across the network. + +You'll see that the `arp_test()` function sends our first ARP this way. We tell it the IP of our router (`192.168.0.1` in my case), but we don't know its MAC address - that's what we want to find out. Once the ARP is sent, `arp_test()` then waits for received Ethernet packets, checks whether they're for us and, if they come from the router's IP address (therefore likely to be the ARP response to our request), we print out the router's MAC address. + +This fulfils design requirement 3, and therefore we're done! All we need to do is ensure that _kernel/kernel.c_ calls our networking routines in the right order. I've chosen to do this on core 3 with a few easy-to-follow changes from where we left off in _part13-interrupts_. Essentially these are all the calls we need: + +```c +spi_init(); +init_network(); +arp_test(); +``` + +_Imagine how happy I was to see my router's (correct!) MAC address appear on-screen - a sign of life, and proof that my OS is actually networking!_ + +![ARP response](images/14-spi-ethernet-arp.jpg) + +[Go to part15-tcpip-webserver >](../part15-tcpip-webserver) diff --git a/part14-spi-ethernet-interrupt/boot/boot.S b/part14-spi-ethernet-interrupt/boot/boot.S new file mode 100644 index 0000000..f644aee --- /dev/null +++ b/part14-spi-ethernet-interrupt/boot/boot.S @@ -0,0 +1,114 @@ +#include "sysregs.h" + +#define LOCAL_CONTROL 0xff800000 +#define LOCAL_PRESCALER 0xff800008 +#define OSC_FREQ 54000000 +#define MAIN_STACK 0x400000 + +.section ".text.boot" // Make sure the linker puts this at the start of the kernel image + +.global _start // Execution starts here + +_start: + ldr x0, =LOCAL_CONTROL // Sort out the timer + str wzr, [x0] + mov w1, 0x80000000 + str w1, [x0, #(LOCAL_PRESCALER - LOCAL_CONTROL)] + + ldr x0, =OSC_FREQ + msr cntfrq_el0, x0 + msr cntvoff_el2, xzr + + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + + // We're not on the main core, so hang in an infinite wait loop + adr x5, spin_cpu0 +1: wfe + ldr x4, [x5, x1, lsl #3] + cbz x4, 1b + + ldr x2, =__stack_start // Get ourselves a fresh stack - location depends on CPU core asking + lsl x1, x1, #9 // Multiply core_number by 512 + add x3, x2, x1 // Add to the address + mov sp, x3 + + mov x0, #0 + mov x1, #0 + mov x2, #0 + mov x3, #0 + br x4 + b 1b +2: // We're on the main core! + // First enable the FPU + + mov x0, #0x33ff + msr cptr_el3, x0 // Disable coprocessor traps to EL3 + mov x0, #3 << 20 + msr cpacr_el1, x0 // Enable FP/SIMD at EL1 + + // Now get ready to switch from EL3 down to EL1 + + ldr x0, =SCTLR_VALUE_MMU_DISABLED + msr sctlr_el1, x0 + + ldr x0, =HCR_VALUE + msr hcr_el2, x0 + + ldr x0, =SCR_VALUE + msr scr_el3, x0 + + ldr x0, =SPSR_VALUE + msr spsr_el3, x0 + + adr x0, el1_entry + msr elr_el3, x0 + + eret +el1_entry: + // We're in EL1 + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Set stack to start somewhere safe + mov sp, #MAIN_STACK + + // Jump to our main() routine in C (make sure it doesn't return) +4: bl main + // In case it does return, halt the master core too + b 1b + +.ltorg + +.org 0x110 +.globl spin_cpu0 +spin_cpu0: + .quad 0 + +.org 0x118 +.globl spin_cpu1 +spin_cpu1: + .quad 0 + +.org 0x120 +.globl spin_cpu2 +spin_cpu2: + .quad 0 + +.org 0x128 +.globl spin_cpu3 +spin_cpu3: + .quad 0 + +.globl get_el +get_el: + mrs x0, CurrentEL + lsr x0, x0, #2 + ret diff --git a/part14-spi-ethernet-interrupt/boot/link.ld b/part14-spi-ethernet-interrupt/boot/link.ld new file mode 100644 index 0000000..4e84390 --- /dev/null +++ b/part14-spi-ethernet-interrupt/boot/link.ld @@ -0,0 +1,35 @@ +SECTIONS +{ + .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } + PROVIDE(_data = .); + .data : { *(.data .data.* .gnu.linkonce.d*) } + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + __bss_size = (__bss_end - __bss_start)>>3; + } + .cpu1Stack : + { + . = ALIGN(16); + __stack_start = .; + . = . + 512; + __cpu1_stack = .; + } + .cpu2Stack : + { + . = . + 512; + __cpu2_stack = .; + } + .cpu3Stack : + { + . = . + 512; + __cpu3_stack = .; + } + _end = .; + + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} diff --git a/part14-spi-ethernet-interrupt/boot/sysregs.h b/part14-spi-ethernet-interrupt/boot/sysregs.h new file mode 100644 index 0000000..385cd79 --- /dev/null +++ b/part14-spi-ethernet-interrupt/boot/sysregs.h @@ -0,0 +1,44 @@ +#ifndef _SYSREGS_H +#define _SYSREGS_H + +// *************************************** +// SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. +// *************************************** + +#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) +#define SCTLR_EE_LITTLE_ENDIAN (0 << 25) +#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) +#define SCTLR_I_CACHE_DISABLED (0 << 12) +#define SCTLR_D_CACHE_DISABLED (0 << 2) +#define SCTLR_I_CACHE_ENABLED (1 << 12) +#define SCTLR_D_CACHE_ENABLED (1 << 2) +#define SCTLR_MMU_DISABLED (0 << 0) +#define SCTLR_MMU_ENABLED (1 << 0) + +#define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_ENABLED | SCTLR_D_CACHE_ENABLED | SCTLR_MMU_DISABLED) + +// *************************************** +// HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. +// *************************************** + +#define HCR_RW (1 << 31) +#define HCR_VALUE HCR_RW + +// *************************************** +// SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. +// *************************************** + +#define SCR_RESERVED (3 << 4) +#define SCR_RW (1 << 10) +#define SCR_NS (1 << 0) +#define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) + +// *************************************** +// SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. +// *************************************** + +#define SPSR_MASK_ALL (7 << 6) +#define SPSR_EL1h (5 << 0) +#define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) + +#endif diff --git a/part14-spi-ethernet-interrupt/config.txt b/part14-spi-ethernet-interrupt/config.txt new file mode 100644 index 0000000..c5cb68b --- /dev/null +++ b/part14-spi-ethernet-interrupt/config.txt @@ -0,0 +1,48 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +kernel_old=1 +disable_commandline_tags=1 +core_freq_min=500 +hdmi_group=1 +hdmi_mode=16 + diff --git a/part14-spi-ethernet-interrupt/images/14-spi-ethernet-arp.jpg b/part14-spi-ethernet-interrupt/images/14-spi-ethernet-arp.jpg new file mode 100644 index 0000000..69dbd1c Binary files /dev/null and b/part14-spi-ethernet-interrupt/images/14-spi-ethernet-arp.jpg differ diff --git a/part14-spi-ethernet-interrupt/images/14-spi-ethernet-photo.jpg b/part14-spi-ethernet-interrupt/images/14-spi-ethernet-photo.jpg new file mode 100644 index 0000000..c3d22f7 Binary files /dev/null and b/part14-spi-ethernet-interrupt/images/14-spi-ethernet-photo.jpg differ diff --git a/part14-spi-ethernet-interrupt/include/io.h b/part14-spi-ethernet-interrupt/include/io.h new file mode 100644 index 0000000..0c79113 --- /dev/null +++ b/part14-spi-ethernet-interrupt/include/io.h @@ -0,0 +1,19 @@ +#define PERIPHERAL_BASE 0xFE000000 +#define LEGACY_BASE 0x7E000000 +#define SAFE_ADDRESS 0x00400000 // Somewhere safe to store a lot of data + +void uart_init(); +void uart_writeText(char *buffer); +void uart_loadOutputFifo(); +unsigned char uart_readByte(); +unsigned int uart_isReadByteReady(); +void uart_writeByteBlockingActual(unsigned char ch); +void uart_update(); +void mmio_write(long reg, unsigned int val); +unsigned int mmio_read(long reg); +void gpio_useAsAlt0(unsigned int pin_number); +void gpio_useAsAlt3(unsigned int pin_number); +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff); +void gpio_initOutputPinWithPullNone(unsigned int pin_number); +void uart_hex(unsigned int d); +void uart_byte(unsigned char b); diff --git a/part14-spi-ethernet-interrupt/include/multicore.h b/part14-spi-ethernet-interrupt/include/multicore.h new file mode 100644 index 0000000..4c3dc0f --- /dev/null +++ b/part14-spi-ethernet-interrupt/include/multicore.h @@ -0,0 +1,11 @@ +extern unsigned int spin_cpu0; +extern unsigned int spin_cpu1; +extern unsigned int spin_cpu2; +extern unsigned int spin_cpu3; + +void start_core1(void (*func)(void)); +void start_core2(void (*func)(void)); +void start_core3(void (*func)(void)); +void clear_core1(void); +void clear_core2(void); +void clear_core3(void); diff --git a/part14-spi-ethernet-interrupt/include/spi.h b/part14-spi-ethernet-interrupt/include/spi.h new file mode 100644 index 0000000..bf57e3e --- /dev/null +++ b/part14-spi-ethernet-interrupt/include/spi.h @@ -0,0 +1,5 @@ +void spi_init(); +void spi_send_recv(unsigned char *sbuffer, unsigned char *rbuffer, unsigned int size); +void spi_send(unsigned char *data, unsigned int size); +void spi_recv(unsigned char *data, unsigned int size); +void spi_chip_select(unsigned char chip_select); diff --git a/part14-spi-ethernet-interrupt/kernel/arp.c b/part14-spi-ethernet-interrupt/kernel/arp.c new file mode 100644 index 0000000..c43b2ef --- /dev/null +++ b/part14-spi-ethernet-interrupt/kernel/arp.c @@ -0,0 +1,229 @@ +#include "../net/enc28j60.h" +#include "../include/io.h" + +ENC_HandleTypeDef handle; + +// Structure for Ethernet header + +typedef struct { + uint8_t DestAddrs[6]; + uint8_t SrcAddrs[6]; + uint16_t type; +} EtherNetII; + +// Ethernet packet types + +#define ARPPACKET 0x0608 +#define IPPACKET 0x0008 + +// Structure for an ARP Packet + +typedef struct { + EtherNetII eth; + uint16_t hardware; + uint16_t protocol; + uint8_t hardwareSize; + uint8_t protocolSize; + uint16_t opCode; + uint8_t senderMAC[6]; + uint8_t senderIP[4]; + uint8_t targetMAC[6]; + uint8_t targetIP[4]; +} ARP; + +// ARP OpCodes + +#define ARPREPLY 0x0200 +#define ARPREQUEST 0x0100 + +// ARP hardware types + +#define ETHERNET 0x0100 + +// MAC address to be assigned to the ENC28J60 + +uint8_t myMAC[6] = { 0xc0, 0xff, 0xee, 0xc0, 0xff, 0xee }; + +// Router MAC is not known to start with, and requires an ARP reply to find out + +uint8_t routerMAC[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + +// IP address to be assigned to the ENC28J60 + +uint8_t deviceIP[4] = { 10, 7, 3, 20 }; + +// IP Address of the router, whose hardware address we will find using the ARP request + +uint8_t routerIP[4] = { 10, 7, 3, 18 }; + +// HELPER FUNCTIONS + +void *memset(void *dest, uint8_t val, uint16_t len) +{ + uint8_t *ptr = dest; + while (len-- > 0) + *ptr++ = val; + return dest; +} + +void *memcpy(void *dest, const void *src, uint16_t len) +{ + uint8_t *d = dest; + const uint8_t *s = src; + while (len--) + *d++ = *s++; + return dest; +} + +uint8_t memcmp(void *str1, void *str2, unsigned count) +{ + uint8_t *s1 = str1; + uint8_t *s2 = str2; + + while (count-- > 0) + { + if (*s1++ != *s2++) + return s1[-1] < s2[-1] ? -1 : 1; + } + + return 0; +} + +// MAIN FUNCTIONS + +void SendArpPacket(uint8_t *targetIP, uint8_t *deviceMAC) +{ + /* Parameters: + * targetIP - The target IP Address for the ARP request (the one whose hardware + * address we want) + * deviceMAC - The MAC address of the ENC28J60, i.e. the source MAC for the ARP + * request + */ + + ARP arpPacket; + + // The source of the packet will be the ENC28J60 MAC address + memcpy(arpPacket.eth.SrcAddrs, deviceMAC, 6); + + // The destination is broadcast - a MAC address of FF:FF:FF:FF:FF:FF + memset(arpPacket.eth.DestAddrs, 0xFF, 6); + + arpPacket.eth.type = ARPPACKET; + arpPacket.hardware = ETHERNET; + + // We want an IP address resolved + + arpPacket.protocol = IPPACKET; + arpPacket.hardwareSize = 0x06; // sizeof(deviceMAC); + arpPacket.protocolSize = 0x04; // sizeof(deviceIP); + arpPacket.opCode = ARPREQUEST; + + // Target MAC is set to 0 as it is unknown + memset(arpPacket.targetMAC, 0, 6); + + // Sender MAC is the ENC28J60's MAC address + memcpy(arpPacket.senderMAC, deviceMAC, 6); + + // The target IP is the IP address we want resolved + memcpy(arpPacket.targetIP, targetIP, 4); + + // Check if the last reply has come from an IP address that we want i.e. someone else is already using it + if (!memcmp(targetIP, deviceIP, 4)) { + // Yes, someone is using our IP so set the sender IP to 0.0.0.0 + memset(arpPacket.senderIP, 0, 4); + } else { + // No, nobody is using our IP so we can use it confidently + memcpy(arpPacket.senderIP, deviceIP, 4); + } + + // Send the packet + + if (ENC_RestoreTXBuffer(&handle, sizeof(ARP)) == 0) { + uart_writeText("Sending ARP request.\n"); + + /*uart_writeText("My MAC is "); + uart_hex(myMAC[0]); + uart_writeText(":"); + uart_hex(myMAC[1]); + uart_writeText(":"); + uart_hex(myMAC[2]); + uart_writeText(":"); + uart_hex(myMAC[3]); + uart_writeText(":"); + uart_hex(myMAC[4]); + uart_writeText(":"); + uart_hex(myMAC[5]); + uart_writeText("\n");*/ + + ENC_WriteBuffer((unsigned char *)&arpPacket, sizeof(ARP)); + handle.transmitLength = sizeof(ARP); + + ENC_Transmit(&handle); + } +} + +void arp_test(void) +{ + ARP *checkPacket; + + SendArpPacket(routerIP, myMAC); + + uart_writeText("Waiting for ARP response.\n"); + + while (1) { + while (!ENC_GetReceivedFrame(&handle)); + + uint16_t len = handle.RxFrameInfos.length; + uint8_t *buffer = (uint8_t *)handle.RxFrameInfos.buffer; + checkPacket = (ARP *)buffer; + + if (len > 0) { + if (!memcmp(checkPacket->senderIP, routerIP, 4)) { + // Success! We have found our router's MAC address + + memcpy(routerMAC, checkPacket->senderMAC, 6); + uart_writeText("Router MAC is "); + uart_hex(routerMAC[0]); + uart_writeText(":"); + uart_hex(routerMAC[1]); + uart_writeText(":"); + uart_hex(routerMAC[2]); + uart_writeText(":"); + uart_hex(routerMAC[3]); + uart_writeText(":"); + uart_hex(routerMAC[4]); + uart_writeText(":"); + uart_hex(routerMAC[5]); + uart_writeText("\n"); + + break; + } + } + } +} + +void init_network(void) +{ + handle.Init.DuplexMode = ETH_MODE_HALFDUPLEX; + handle.Init.MACAddr = myMAC; + handle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; + handle.Init.InterruptEnableBits = EIE_LINKIE | EIE_PKTIE; + + uart_writeText("Starting network up.\n"); + if (!ENC_Start(&handle)) { + uart_writeText("could not initialise network card.\n"); + } else { + uart_writeText("Setting MAC address to C0:FF:FE:C0:FF:FE.\n"); + + ENC_SetMacAddr(&handle); + + uart_writeText("Network card successfully initialised.\n"); + } + + uart_writeText("Waiting for ifup... \n"); + while (!(handle.LinkStatus & PHSTAT2_LSTAT)) ENC_IRQHandler(&handle); + uart_writeText("done.\n"); + + // Re-enable global interrupts + ENC_EnableInterrupts(EIE_INTIE); +} diff --git a/part14-spi-ethernet-interrupt/kernel/irq.c b/part14-spi-ethernet-interrupt/kernel/irq.c new file mode 100644 index 0000000..6e83ca3 --- /dev/null +++ b/part14-spi-ethernet-interrupt/kernel/irq.c @@ -0,0 +1,27 @@ +#include "kernel.h" + +void enable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3; +} + +void disable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = 0; +} + +void handle_irq() { + unsigned int irq = REGS_IRQ->irq0_pending_0; + + while(irq & (SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3)) { + if (irq & SYS_TIMER_IRQ_1) { + irq &= ~SYS_TIMER_IRQ_1; + + handle_timer_1(); + } + + if (irq & SYS_TIMER_IRQ_3) { + irq &= ~SYS_TIMER_IRQ_3; + + handle_timer_3(); + } + } +} diff --git a/part14-spi-ethernet-interrupt/kernel/irqentry.S b/part14-spi-ethernet-interrupt/kernel/irqentry.S new file mode 100644 index 0000000..d24ae00 --- /dev/null +++ b/part14-spi-ethernet-interrupt/kernel/irqentry.S @@ -0,0 +1,160 @@ +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +//stack frame size +#define S_FRAME_SIZE 256 + +.macro kernel_entry + sub sp, sp, #S_FRAME_SIZE + stp x0, x1, [sp, #16 * 0] + stp x2, x3, [sp, #16 * 1] + stp x4, x5, [sp, #16 * 2] + stp x6, x7, [sp, #16 * 3] + stp x8, x9, [sp, #16 * 4] + stp x10, x11, [sp, #16 * 5] + stp x12, x13, [sp, #16 * 6] + stp x14, x15, [sp, #16 * 7] + stp x16, x17, [sp, #16 * 8] + stp x18, x19, [sp, #16 * 9] + stp x20, x21, [sp, #16 * 10] + stp x22, x23, [sp, #16 * 11] + stp x24, x25, [sp, #16 * 12] + stp x26, x27, [sp, #16 * 13] + stp x28, x29, [sp, #16 * 14] + str x30, [sp, #16 * 15] +.endm + +.macro kernel_exit + ldp x0, x1, [sp, #16 * 0] + ldp x2, x3, [sp, #16 * 1] + ldp x4, x5, [sp, #16 * 2] + ldp x6, x7, [sp, #16 * 3] + ldp x8, x9, [sp, #16 * 4] + ldp x10, x11, [sp, #16 * 5] + ldp x12, x13, [sp, #16 * 6] + ldp x14, x15, [sp, #16 * 7] + ldp x16, x17, [sp, #16 * 8] + ldp x18, x19, [sp, #16 * 9] + ldp x20, x21, [sp, #16 * 10] + ldp x22, x23, [sp, #16 * 11] + ldp x24, x25, [sp, #16 * 12] + ldp x26, x27, [sp, #16 * 13] + ldp x28, x29, [sp, #16 * 14] + ldr x30, [sp, #16 * 15] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.macro handle_invalid_entry type + kernel_entry + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + + // We could pass this to a function to print an error here + // e.g. bl show_invalid_entry_message + // + // For now we'll just hang + + b err_hang +.endm + +.macro ventry label +.align 7 + b \label +.endm + +//Exception vectors table +.align 11 +.globl vectors +vectors: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry handle_el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 + ventry irq_invalid_el0_64 // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + + +sync_invalid_el1t: + handle_invalid_entry SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry SYNC_INVALID_EL1h + +fiq_invalid_el1h: + handle_invalid_entry FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry ERROR_INVALID_EL1h + +sync_invalid_el0_64: + handle_invalid_entry SYNC_INVALID_EL0_64 + +irq_invalid_el0_64: + handle_invalid_entry IRQ_INVALID_EL0_64 + +fiq_invalid_el0_64: + handle_invalid_entry FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry ERROR_INVALID_EL0_32 + +handle_el1_irq: + kernel_entry + bl handle_irq + kernel_exit + +.globl err_hang +err_hang: b err_hang diff --git a/part14-spi-ethernet-interrupt/kernel/kernel.c b/part14-spi-ethernet-interrupt/kernel/kernel.c new file mode 100644 index 0000000..41d3d6e --- /dev/null +++ b/part14-spi-ethernet-interrupt/kernel/kernel.c @@ -0,0 +1,150 @@ +#include "../include/io.h" +#include "../include/spi.h" +#include "../include/multicore.h" +#include "kernel.h" + +void wait_msec(unsigned int n) +{ + register unsigned long f, t, r; + + // Get the current counter frequency + asm volatile ("mrs %0, cntfrq_el0" : "=r"(f)); + // Read the current counter + asm volatile ("mrs %0, cntpct_el0" : "=r"(t)); + // Calculate expire value for counter + t+=((f/1000)*n)/1000; + do{asm volatile ("mrs %0, cntpct_el0" : "=r"(r));}while(rcounter_lo; + timer1_val += timer1_int; + REGS_TIMER->compare[1] = timer1_val; + + timer3_val = REGS_TIMER->counter_lo; + timer3_val += timer3_int; + REGS_TIMER->compare[3] = timer3_val; +} + +void handle_timer_1() { + timer1_val += timer1_int; + REGS_TIMER->compare[1] = timer1_val; + REGS_TIMER->control_status |= SYS_TIMER_IRQ_1; + + unsigned int progval = timer1_val / timer1_int; + if (progval <= 100) { + //drawProgress(2, progval); + } else { + uart_writeText("Timer 1 done.\n"); + } +} + +void handle_timer_3() { + timer3_val += timer3_int; + REGS_TIMER->compare[3] = timer3_val; + REGS_TIMER->control_status |= SYS_TIMER_IRQ_3; + + unsigned int progval = timer3_val / timer3_int; + if (progval <= 100); // drawProgress(3, progval); +} + +unsigned long HAL_GetTick(void) { + unsigned int hi = REGS_TIMER->counter_hi; + unsigned int lo = REGS_TIMER->counter_lo; + + //double check hi value didn't change after setting it... + if (hi != REGS_TIMER->counter_hi) { + hi = REGS_TIMER->counter_hi; + lo = REGS_TIMER->counter_lo; + } + + return ((unsigned long)hi << 32) | lo; +} + +void HAL_Delay(unsigned int ms) { + unsigned long start = HAL_GetTick(); + + while(HAL_GetTick() < start + (ms * 1000)); +} + +void main(void) +{ + uart_init(); + uart_writeText("uart initialised.\n"); + + //uart_writeText("Kick it off on core 1\n"); + //start_core1(core1_main); + + // Kick off the timers + + irq_init_vectors(); + enable_interrupt_controller(); + irq_barrier(); + irq_enable(); + timer_init(); + + uart_writeText("Kick it off on core 3\n"); + + start_core3(core3_main); + + //uart_writeText("Kick it off on core 0\n"); + //core0_main(); + + // Disable IRQs and loop endlessly + + irq_disable(); + disable_interrupt_controller(); + + while(1); +} diff --git a/part14-spi-ethernet-interrupt/kernel/kernel.h b/part14-spi-ethernet-interrupt/kernel/kernel.h new file mode 100644 index 0000000..ad56d4d --- /dev/null +++ b/part14-spi-ethernet-interrupt/kernel/kernel.h @@ -0,0 +1,51 @@ +#define PERIPHERAL_BASE 0xFE000000 +#define CLOCKHZ 1000000 + +struct timer_regs { + volatile unsigned int control_status; + volatile unsigned int counter_lo; + volatile unsigned int counter_hi; + volatile unsigned int compare[4]; +}; + +#define REGS_TIMER ((struct timer_regs *)(PERIPHERAL_BASE + 0x00003000)) + +struct arm_irq_regs_2711 { + volatile unsigned int irq0_pending_0; + volatile unsigned int irq0_pending_1; + volatile unsigned int irq0_pending_2; + volatile unsigned int res0; + volatile unsigned int irq0_enable_0; + volatile unsigned int irq0_enable_1; + volatile unsigned int irq0_enable_2; + volatile unsigned int res1; + volatile unsigned int irq0_disable_0; + volatile unsigned int irq0_disable_1; + volatile unsigned int irq0_disable_2; +}; + +typedef struct arm_irq_regs_2711 arm_irq_regs; + +#define REGS_IRQ ((arm_irq_regs *)(PERIPHERAL_BASE + 0x0000B200)) + +enum vc_irqs { + SYS_TIMER_IRQ_0 = 1, + SYS_TIMER_IRQ_1 = 2, + SYS_TIMER_IRQ_2 = 4, + SYS_TIMER_IRQ_3 = 8, + AUX_IRQ = (1 << 29) +}; + +void irq_init_vectors(); +void irq_enable(); +void irq_barrier(); +void irq_disable(); +void enable_interrupt_controller(); +void disable_interrupt_controller(); + +void handle_timer_1(); +void handle_timer_3(); + +void init_network(void); +void arp_test(void); +void wait_msec(unsigned int n); \ No newline at end of file diff --git a/part14-spi-ethernet-interrupt/kernel/utils.S b/part14-spi-ethernet-interrupt/kernel/utils.S new file mode 100644 index 0000000..7a34152 --- /dev/null +++ b/part14-spi-ethernet-interrupt/kernel/utils.S @@ -0,0 +1,20 @@ +.globl irq_init_vectors +irq_init_vectors: + adr x0, vectors + msr vbar_el1, x0 + ret + +.globl irq_enable +irq_enable: + msr daifclr, #2 + ret + +.globl irq_disable +irq_disable: + msr daifset, #2 + ret + +.globl irq_barrier +irq_barrier: + dsb sy + ret diff --git a/part14-spi-ethernet-interrupt/lib/io.c b/part14-spi-ethernet-interrupt/lib/io.c new file mode 100644 index 0000000..34a02aa --- /dev/null +++ b/part14-spi-ethernet-interrupt/lib/io.c @@ -0,0 +1,199 @@ +#include "../include/io.h" + +// GPIO + +enum { + GPFSEL0 = PERIPHERAL_BASE + 0x200000, + GPSET0 = PERIPHERAL_BASE + 0x20001C, + GPCLR0 = PERIPHERAL_BASE + 0x200028, + GPPUPPDN0 = PERIPHERAL_BASE + 0x2000E4 +}; + +enum { + GPIO_MAX_PIN = 53, + GPIO_FUNCTION_OUT = 1, + GPIO_FUNCTION_ALT5 = 2, + GPIO_FUNCTION_ALT3 = 7, + GPIO_FUNCTION_ALT0 = 4 +}; + +enum { + Pull_None = 0, + Pull_Down = 1, // Are down and up the right way around? + Pull_Up = 2 +}; + +void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } +unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; } + +unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) { + unsigned int field_mask = (1 << field_size) - 1; + + if (pin_number > field_max) return 0; + if (value > field_mask) return 0; + + unsigned int num_fields = 32 / field_size; + unsigned int reg = base + ((pin_number / num_fields) * 4); + unsigned int shift = (pin_number % num_fields) * field_size; + + unsigned int curval = mmio_read(reg); + curval &= ~(field_mask << shift); + curval |= value << shift; + mmio_write(reg, curval); + + return 1; +} + +unsigned int gpio_set (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); } +unsigned int gpio_clear (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); } +unsigned int gpio_pull (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); } +unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); } + +void gpio_useAsAlt0(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT0); +} + +void gpio_useAsAlt3(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT3); +} + +void gpio_useAsAlt5(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT5); +} + +void gpio_initOutputPinWithPullNone(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_OUT); +} + +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff) { + if (onOrOff) { + gpio_set(pin_number, 1); + } else { + gpio_clear(pin_number, 1); + } +} + +// UART + +enum { + AUX_BASE = PERIPHERAL_BASE + 0x215000, + AUX_IRQ = AUX_BASE, + AUX_ENABLES = AUX_BASE + 4, + AUX_MU_IO_REG = AUX_BASE + 64, + AUX_MU_IER_REG = AUX_BASE + 68, + AUX_MU_IIR_REG = AUX_BASE + 72, + AUX_MU_LCR_REG = AUX_BASE + 76, + AUX_MU_MCR_REG = AUX_BASE + 80, + AUX_MU_LSR_REG = AUX_BASE + 84, + AUX_MU_MSR_REG = AUX_BASE + 88, + AUX_MU_SCRATCH = AUX_BASE + 92, + AUX_MU_CNTL_REG = AUX_BASE + 96, + AUX_MU_STAT_REG = AUX_BASE + 100, + AUX_MU_BAUD_REG = AUX_BASE + 104, + AUX_UART_CLOCK = 500000000, + UART_MAX_QUEUE = 16 * 1024 +}; + +#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1) + +unsigned char uart_output_queue[UART_MAX_QUEUE]; +unsigned int uart_output_queue_write = 0; +unsigned int uart_output_queue_read = 0; + +void uart_init() { + mmio_write(AUX_ENABLES, 1); //enable UART1 + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_CNTL_REG, 0); + mmio_write(AUX_MU_LCR_REG, 3); //8 bits + mmio_write(AUX_MU_MCR_REG, 0); + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts + mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200)); + gpio_useAsAlt5(14); + gpio_useAsAlt5(15); + mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX +} + +unsigned int uart_isOutputQueueEmpty() { + return uart_output_queue_read == uart_output_queue_write; +} + +unsigned int uart_isReadByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x01; } +unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; } + +unsigned char uart_readByte() { + while (!uart_isReadByteReady()); + return (unsigned char)mmio_read(AUX_MU_IO_REG); +} + +void uart_writeByteBlockingActual(unsigned char ch) { + while (!uart_isWriteByteReady()); + mmio_write(AUX_MU_IO_REG, (unsigned int)ch); +} + +void uart_loadOutputFifo() { + while (!uart_isOutputQueueEmpty() && uart_isWriteByteReady()) { + uart_writeByteBlockingActual(uart_output_queue[uart_output_queue_read]); + uart_output_queue_read = (uart_output_queue_read + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + } +} + +void uart_writeByteBlocking(unsigned char ch) { + unsigned int next = (uart_output_queue_write + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + + while (next == uart_output_queue_read) uart_loadOutputFifo(); + + uart_output_queue[uart_output_queue_write] = ch; + uart_output_queue_write = next; +} + +void uart_writeText(char *buffer) { + while (*buffer) { + if (*buffer == '\n') uart_writeByteBlockingActual('\r'); + uart_writeByteBlockingActual(*buffer++); + } +} + +void uart_drainOutputQueue() { + while (!uart_isOutputQueueEmpty()) uart_loadOutputFifo(); +} + +void uart_update() { + uart_loadOutputFifo(); + + if (uart_isReadByteReady()) { + unsigned char ch = uart_readByte(); + if (ch == '\r') uart_writeText("\n"); else uart_writeByteBlocking(ch); + } +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + // get highest tetrad + n=(d>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlockingActual(n); + } +} + +void uart_byte(unsigned char b) { + unsigned int n; + int c; + for(c=4;c>=0;c-=4) { + // get highest tetrad + n=(b>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlockingActual(n); + } + uart_writeByteBlockingActual(' '); +} diff --git a/part14-spi-ethernet-interrupt/lib/mb.c b/part14-spi-ethernet-interrupt/lib/mb.c new file mode 100644 index 0000000..65dbbb8 --- /dev/null +++ b/part14-spi-ethernet-interrupt/lib/mb.c @@ -0,0 +1,39 @@ +#include "../include/io.h" + +// The buffer must be 16-byte aligned as only the upper 28 bits of the address can be passed via the mailbox +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +enum { + VIDEOCORE_MBOX = (PERIPHERAL_BASE + 0x0000B880), + MBOX_READ = (VIDEOCORE_MBOX + 0x0), + MBOX_POLL = (VIDEOCORE_MBOX + 0x10), + MBOX_SENDER = (VIDEOCORE_MBOX + 0x14), + MBOX_STATUS = (VIDEOCORE_MBOX + 0x18), + MBOX_CONFIG = (VIDEOCORE_MBOX + 0x1C), + MBOX_WRITE = (VIDEOCORE_MBOX + 0x20), + MBOX_RESPONSE = 0x80000000, + MBOX_FULL = 0x80000000, + MBOX_EMPTY = 0x40000000 +}; + +unsigned int mbox_call(unsigned char ch) +{ + // 28-bit address (MSB) and 4-bit value (LSB) + unsigned int r = ((unsigned int)((long) &mbox) &~ 0xF) | (ch & 0xF); + + // Wait until we can write + while (mmio_read(MBOX_STATUS) & MBOX_FULL); + + // Write the address of our buffer to the mailbox with the channel appended + mmio_write(MBOX_WRITE, r); + + while (1) { + // Is there a reply? + while (mmio_read(MBOX_STATUS) & MBOX_EMPTY); + + // Is it a reply to our message? + if (r == mmio_read(MBOX_READ)) return mbox[1]==MBOX_RESPONSE; // Is it successful? + + } + return 0; +} diff --git a/part14-spi-ethernet-interrupt/lib/multicore.c b/part14-spi-ethernet-interrupt/lib/multicore.c new file mode 100644 index 0000000..0000bcd --- /dev/null +++ b/part14-spi-ethernet-interrupt/lib/multicore.c @@ -0,0 +1,44 @@ +#include "../include/multicore.h" + +void store32(unsigned long address, unsigned long value) +{ + *(unsigned long *) address = value; +} + +unsigned long load32(unsigned long address) +{ + return *(unsigned long *) address; +} + +void start_core1(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu1, (unsigned long)func); + asm volatile ("sev"); +} + +void start_core2(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu2, (unsigned long)func); + asm volatile ("sev"); +} + +void start_core3(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu3, (unsigned long)func); + asm volatile ("sev"); +} + +void clear_core1(void) +{ + store32((unsigned long)&spin_cpu1, 0); +} + +void clear_core2(void) +{ + store32((unsigned long)&spin_cpu2, 0); +} + +void clear_core3(void) +{ + store32((unsigned long)&spin_cpu3, 0); +} diff --git a/part14-spi-ethernet-interrupt/lib/spi.c b/part14-spi-ethernet-interrupt/lib/spi.c new file mode 100644 index 0000000..09085ef --- /dev/null +++ b/part14-spi-ethernet-interrupt/lib/spi.c @@ -0,0 +1,99 @@ +#include "../include/io.h" + +// SPI + +struct Spi0Regs { + volatile unsigned int cs; + volatile unsigned int fifo; + volatile unsigned int clock; + volatile unsigned int data_length; + volatile unsigned int ltoh; + volatile unsigned int dc; +}; + +#define REGS_SPI0 ((struct Spi0Regs *)(PERIPHERAL_BASE + 0x00204000)) + +// CS Register +#define CS_LEN_LONG (1 << 25) +#define CS_DMA_LEN (1 << 24) +#define CS_CSPOL2 (1 << 23) +#define CS_CSPOL1 (1 << 22) +#define CS_CSPOL0 (1 << 21) +#define CS_RXF (1 << 20) +#define CS_RXR (1 << 19) +#define CS_TXD (1 << 18) +#define CS_RXD (1 << 17) +#define CS_DONE (1 << 16) +#define CS_LEN (1 << 13) +#define CS_REN (1 << 12) +#define CS_ADCS (1 << 11) +#define CS_INTR (1 << 10) +#define CS_INTD (1 << 9) +#define CS_DMAEN (1 << 8) +#define CS_TA (1 << 7) +#define CS_CSPOL (1 << 6) +#define CS_CLEAR_RX (1 << 5) +#define CS_CLEAR_TX (1 << 4) +#define CS_CPOL__SHIFT 3 +#define CS_CPHA__SHIFT 2 +#define CS_CS (1 << 0) +#define CS_CS__SHIFT 0 + +void spi_init() { + gpio_useAsAlt0(7); //CS1 + gpio_initOutputPinWithPullNone(8); //CS0 + gpio_useAsAlt0(9); //MISO + gpio_useAsAlt0(10); //MOSI + gpio_useAsAlt0(11); //SCLK +} + +void spi_chip_select(unsigned char chip_select) { + gpio_setPinOutputBool(8, chip_select); +} + +void spi_send_recv(unsigned char *sbuffer, unsigned char *rbuffer, unsigned int size) { + REGS_SPI0->data_length = size; + REGS_SPI0->cs = REGS_SPI0->cs | CS_CLEAR_RX | CS_CLEAR_TX | CS_TA; + + unsigned int read_count = 0; + unsigned int write_count = 0; + + while(read_count < size || write_count < size) { + while(write_count < size && REGS_SPI0->cs & CS_TXD) { + if (sbuffer) { + REGS_SPI0->fifo = *sbuffer++; + } else { + REGS_SPI0->fifo = 0; + } + + write_count++; + } + + while(read_count < size && REGS_SPI0->cs & CS_RXD) { + unsigned int data = REGS_SPI0->fifo; + + if (rbuffer) { + *rbuffer++ = data; + } + + read_count++; + } + } + + while(!(REGS_SPI0->cs & CS_DONE)) { + while(REGS_SPI0->cs & CS_RXD) { + unsigned int r = REGS_SPI0->fifo; + uart_hex(r); + } + } + + REGS_SPI0->cs = (REGS_SPI0->cs & ~CS_TA); +} + +void spi_send(unsigned char *data, unsigned int size) { + spi_send_recv(data, 0, size); +} + +void spi_recv(unsigned char *data, unsigned int size) { + spi_send_recv(0, data, size); +} diff --git a/part14-spi-ethernet-interrupt/net/enc28j60.c b/part14-spi-ethernet-interrupt/net/enc28j60.c new file mode 100644 index 0000000..b4fab55 --- /dev/null +++ b/part14-spi-ethernet-interrupt/net/enc28j60.c @@ -0,0 +1,1450 @@ +/** + ****************************************************************************** + * @file enc28j60.c + * @author Christian Schoffit, portions from Gregory Nutt: + * Copyright (C) 2010-2012, 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * @version V1.0.0 + * @date 02-June-2015 + * @brief This file provides a set of functions needed to manage the ENC28J60 + * Stand-Alone Ethernet Controller with SPI Interface. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2015 Christian Schoffit

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Christian Schoffit nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* +Module Feature Issue Issue Summary Affected Revisions + B1 B4 B5 B7 +MAC Interface - 1. MAC registers unreliable with slow asynchronous SPI clock X X +Reset - 2. CLKRDY set early X X X X +Core Operating 3. Industrial (-40�C to +85�C) temperature range unsupported X X + Specifications +Oscillator CLKOUT pin 4. CLKOUT unavailable in Power Save mode X X X X +Memory Ethernet 5. Receive buffer must start at 0000h X X X X + Buffer +Interrupts - 6. Receive Packet Pending Interrupt Flag (PKTIF) unreliable X X X X +PHY - 7. TPIN+/- automatic polarity detection and correction + unreliable X X X X +PHY - 8. RBIAS resistor value differs between silicon revisions X X +PHY - 9. Internal loopback in half-duplex unreliable X X X X +PHY - 10. Internal loopback in full-duplex unreliable X X X X +PHY LEDs - 11. Combined Collision and Duplex Status mode unavailable X X X X +Transmit - 12. Transmit abort may stall transmit logic X X X X +Logic +PHY - 13. Received link pulses potentially cause collisions X X +Memory Ethernet 14. Even values in ERXRDPT may corrupt receive buffer X X X X + Buffer +Transmit - 15. LATECOL Status bit unreliable X X X X +Logic +PHY LEDs - 16. LED auto-polarity detection unreliable X X X X +DMA - 17. DMA checksum calculations will abort receive packets X X X X +Receive - 18. Pattern match filter allows reception of extra packets X X X X +Filter +SPI - 19. Reset command unavailable in Power Save mode X X X X +Interface + +Only workaround relative to issues affecting B7 silicon revision are implemented. Therefore, issues +specific to Ethernet conformance are not addressed, since they only affect B1 and B3 silicon revisions. + +Erratas 7, 8, 16... have workaround implemented by hardware + +Errata 18 is implemented in lwip stack +*/ + +/* Includes ------------------------------------------------------------------*/ +#include "enc28j60.h" + +/** @addtogroup BSP + * @{ + */ + +/** @addtogroup Components + * @{ + */ + +/** @defgroup ENC28J60 + * @{ + */ + +/** @defgroup ENC28J60_Private_Types_Definitions + * @{ + */ + +/** @defgroup ENC28J60_Private_Defines + * @{ + */ + +/* Poll timeout */ + +#define ENC_POLLTIMEOUT 50 + +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Macros + * @{ + */ + +/* Packet Memory ************************************************************/ + +/* Packet memory layout */ + +#define ALIGNED_BUFSIZE ((CONFIG_NET_ETH_MTU + 255) & ~255) + +/* Work around Errata #5 (spurious reset of ERXWRPT to 0) by placing the RX + * FIFO at the beginning of packet memory. + */ + +# define PKTMEM_RX_START 0x0000 /* RX buffer must be at addr 0 for errata 5 */ +# define PKTMEM_RX_END (PKTMEM_END-ALIGNED_BUFSIZE) /* RX buffer length is total SRAM minus TX buffer */ +# define PKTMEM_TX_START (PKTMEM_RX_END+1) /* Start TX buffer after */ +# define PKTMEM_TX_ENDP1 (PKTMEM_TX_START+ALIGNED_BUFSIZE) /* Allow TX buffer for two frames */ + +/* Misc. Helper Macros ******************************************************/ + +#define enc_rdgreg(ctrlreg) \ + enc_rdgreg2(ENC_RCR | GETADDR(ctrlreg)) +#define enc_wrgreg(ctrlreg, wrdata) \ + enc_wrgreg2(ENC_WCR | GETADDR(ctrlreg), wrdata) +#define enc_bfcgreg(ctrlreg,clrbits) \ + enc_wrgreg2(ENC_BFC | GETADDR(ctrlreg), clrbits) +#define enc_bfsgreg(ctrlreg,setbits) \ + enc_wrgreg2(ENC_BFS | GETADDR(ctrlreg), setbits) + +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Variables + * @{ + */ + + /* Stores how many iterations the microcontroller can do in 1 �s */ +static uint32_t iter_per_us=0; + +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Function_Prototypes + * @{ + */ +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Functions + * @{ + */ + +/** + Calibrate the constant time + **/ + +static void calibrate(void) +{ + uint32_t time; + volatile uint32_t i; + + iter_per_us = 1000000; + + time = HAL_GetTick(); + /* Wait for next tick */ + while (HAL_GetTick() == time) { + /* wait */ + } + for (i=0; ibank = 0; /* Initialize the trace on the current selected bank */ + //up_mdelay(2); + HAL_Delay(2); /* >1000 �s, conforms to errata #2 */ +} + +/**************************************************************************** + * Function: enc_setbank + * + * Description: + * Set the bank for these next control register access. + * + * Assumption: + * The caller has exclusive access to the SPI bus + * + * Parameters: + * handle - Reference to the driver state structure + * bank - The bank to select (0-3) + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void enc_setbank(ENC_HandleTypeDef *handle, uint8_t bank) { + + if (bank != handle->bank) { + /* Select bank 0 (just so that all of the bits are cleared) */ + + enc_bfcgreg(ENC_ECON1, ECON1_BSEL_MASK); + + /* Then OR in bits to get the correct bank */ + + if (bank != 0) + { + enc_bfsgreg(ENC_ECON1, (bank << ECON1_BSEL_SHIFT)); + } + + /* Then remember the bank setting */ + + handle->bank = bank; + } +} + +/**************************************************************************** + * Function: enc_rdbreg + * + * Description: + * Read from a banked control register using the RCR command. + * + * Parameters: + * handle - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to read + * + * Returned Value: + * The byte read from the banked register + * + * Assumptions: + * + ****************************************************************************/ + +static uint8_t enc_rdbreg(ENC_HandleTypeDef *handle, uint8_t ctrlreg) +{ + uint8_t data[3]; + + /* Set the bank */ + + enc_setbank(handle, GETBANK(ctrlreg)); + + /* Send the RCR command and collect the data. How we collect the data + * depends on if this is a PHY/CAN or not. The normal sequence requires + * 16-clocks: 8 to clock out the cmd and 8 to clock in the data. + */ + + data[0] = ENC_RCR | GETADDR(ctrlreg); + + /* The PHY/MAC sequence requires 24-clocks: 8 to clock out the cmd, + * 8 dummy bits, and 8 to clock in the PHY/MAC data. + */ + + ENC_SPI_SendBuf(data, data, (ISPHYMAC(ctrlreg))?3:2); + return (ISPHYMAC(ctrlreg))?data[2]:data[1]; +} + +/**************************************************************************** + * Function: enc_wrbreg + * + * Description: + * Write to a banked control register using the WCR command. Unlike + * reading, this same SPI sequence works for normal, MAC, and PHY + * registers. + * + * Parameters: + * handle - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to write + * wrdata - The data to send + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrbreg(ENC_HandleTypeDef *handle, uint8_t ctrlreg, + uint8_t wrdata) +{ + uint8_t data[2]; + + /* Set the bank */ + + enc_setbank(handle, GETBANK(ctrlreg)); + + /* Send the WCR command and data. The sequence requires 16-clocks: + * 8 to clock out the cmd + 8 to clock out the data. + */ + + data[0] = ENC_WCR | GETADDR(ctrlreg); + data[1] = wrdata; + + ENC_SPI_SendBuf(data, NULL, 2); +} + +/**************************************************************************** + * Function: enc_waitbreg + * + * Description: + * Wait until banked register bit(s) take a specific value (or a timeout + * occurs). + * + * Parameters: + * handle - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to check + * bits - The bits to check (a mask) + * value - The value of the bits to return (value under mask) + * + * Returned Value: + * OK on success, negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static bool enc_waitbreg(ENC_HandleTypeDef *handle, uint8_t ctrlreg, + uint8_t bits, uint8_t value) +{ + uint32_t start = HAL_GetTick(); + uint32_t elapsed; + uint8_t rddata; + + /* Loop until the exit condition is met */ + + do + { + /* Read the byte from the requested banked register */ + + rddata = enc_rdbreg(handle, ctrlreg); + elapsed = HAL_GetTick() - start; + } + while ((rddata & bits) != value && elapsed < ENC_POLLTIMEOUT); + + return (rddata & bits) == value; +} + + +/**************************************************************************** + * Function: enc_rdphy + * + * Description: + * Read 16-bits of PHY data. + * + * Parameters: + * priv - Reference to the driver state structure + * phyaddr - The PHY register address + * + * Returned Value: + * 16-bit value read from the PHY + * + * Assumptions: + * + ****************************************************************************/ + +static uint16_t enc_rdphy(ENC_HandleTypeDef *handle, uint8_t phyaddr) +{ + uint16_t data = 0; + + /* "To read from a PHY register: + * + * 1. Write the address of the PHY register to read from into the MIREGADR + * register. + */ + + enc_wrbreg(handle, ENC_MIREGADR, phyaddr); + + /* 2. Set the MICMD.MIIRD bit. The read operation begins and the + * MISTAT.BUSY bit is set. + */ + + enc_wrbreg(handle, ENC_MICMD, MICMD_MIIRD); + + /* 3. Wait 10.24 �s. Poll the MISTAT.BUSY bit to be certain that the + * operation is complete. While busy, the host controller should not + * start any MIISCAN operations or write to the MIWRH register. + * + * When the MAC has obtained the register contents, the BUSY bit will + * clear itself. + */ + +// volatile int i; +// for (i=0; i<12*17; i++) { +// } + + up_udelay(12); + + if (enc_waitbreg(handle, ENC_MISTAT, MISTAT_BUSY, 0x00)) + { + /* 4. Clear the MICMD.MIIRD bit. */ + + enc_wrbreg(handle, ENC_MICMD, 0x00); + + /* 5. Read the desired data from the MIRDL and MIRDH registers. The + * order that these bytes are accessed is unimportant." + */ + + data = (uint16_t)enc_rdbreg(handle, ENC_MIRDL); + data |= (uint16_t)enc_rdbreg(handle, ENC_MIRDH) << 8; + } + + return data; +} + +/**************************************************************************** + * Function: enc_wrphy + * + * Description: + * write 16-bits of PHY data. + * + * Parameters: + * handle - Reference to the driver state structure + * phyaddr - The PHY register address + * phydata - 16-bit data to write to the PHY + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrphy(ENC_HandleTypeDef *handle, uint8_t phyaddr, + uint16_t phydata) +{ + /* "To write to a PHY register: + * + * 1. Write the address of the PHY register to write to into the + * MIREGADR register. + */ + + enc_wrbreg(handle, ENC_MIREGADR, phyaddr); + + /* 2. Write the lower 8 bits of data to write into the MIWRL register. */ + + enc_wrbreg(handle, ENC_MIWRL, phydata); + + /* 3. Write the upper 8 bits of data to write into the MIWRH register. + * Writing to this register automatically begins the MIIM transaction, + * so it must be written to after MIWRL. The MISTAT.BUSY bit becomes + * set. + */ + + enc_wrbreg(handle, ENC_MIWRH, phydata >> 8); + + /* The PHY register will be written after the MIIM operation completes, + * which takes 10.24 �s. When the write operation has completed, the BUSY + * bit will clear itself. + * + * The host controller should not start any MIISCAN or MIIRD operations + * while busy." + */ + + /* wait for approx 12 �s */ +// volatile int i; +// for (i=0; i<12*17; i++) { +// } + + up_udelay(12); + enc_waitbreg(handle, ENC_MISTAT, MISTAT_BUSY, 0x00); +} + + +/**************************************************************************** + * Function: enc_pwrfull + * + * Description: + * When normal operation is desired, the host controller must perform + * a slightly modified procedure: + * + * 1. Wake-up by clearing ECON2.PWRSV. + * 2. Wait at least 300 �s for the PHY to stabilize. To accomplish the + * delay, the host controller may poll ESTAT.CLKRDY and wait for it + * to become set. + * 3. Restore receive capability by setting ECON1.RXEN. + * + * After leaving Sleep mode, there is a delay of many milliseconds + * before a new link is established (assuming an appropriate link + * partner is present). The host controller may wish to wait until + * the link is established before attempting to transmit any packets. + * The link status can be determined by polling the PHSTAT2.LSTAT bit. + * Alternatively, the link change interrupt may be used if it is + * enabled. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +/* Power save mode not used (fix errata 4 and 19) */ +#if 0 +static void enc_pwrfull(ENC_HandleTypeDef *handle) +{ + /* 1. Wake-up by clearing ECON2.PWRSV. */ + + enc_bfcgreg(ENC_ECON2, ECON2_PWRSV); + + /* 2. Wait at least 300 �s for the PHY to stabilize. To accomplish the + * delay, the host controller may poll ESTAT.CLKRDY and wait for it to + * become set. + */ + + /* wait for approx 350 �s */ +// volatile int i; +// for (i=0; i<350*17; i++) { +// } + + up_udelay(350); + + enc_waitbreg(handle, ENC_ESTAT, ESTAT_CLKRDY, ESTAT_CLKRDY); + + /* 3. Restore receive capability by setting ECON1.RXEN. + * + * The caller will do this when it is ready to receive packets + */ +} +#endif + +/** + * @brief Initialize the enc28j60 and configure the needed hardware resources + * @param handle: Handle on data configuration. + * @retval None + */ +bool ENC_Start(ENC_HandleTypeDef *handle) +{ + /* register value */ + uint8_t regval; + + /* Calibrate time constant */ + calibrate(); + + /* System reset */ + enc_reset(handle); + + /* Use bank 0 */ + enc_setbank(handle, 0); + + /* Check if we are actually communicating with the ENC28J60. If its + * 0x00 or 0xff, then we are probably not communicating correctly + * via SPI. + */ + + regval = enc_rdbreg(handle, ENC_EREVID); + if (regval == 0x00 || regval == 0xff) { + return false; + } + + /* Initialize ECON2: Enable address auto increment. + */ + + enc_wrgreg(ENC_ECON2, ECON2_AUTOINC /* | ECON2_VRPS*/); + + /* Initialize receive buffer. + * First, set the receive buffer start address. + */ + + handle->nextpkt = PKTMEM_RX_START; + enc_wrbreg(handle, ENC_ERXSTL, PKTMEM_RX_START & 0xff); + enc_wrbreg(handle, ENC_ERXSTH, PKTMEM_RX_START >> 8); + + /* Set the receive data pointer */ + + /* Errata 14 */ + enc_wrbreg(handle, ENC_ERXRDPTL, PKTMEM_RX_END & 0xff); + enc_wrbreg(handle, ENC_ERXRDPTH, PKTMEM_RX_END >> 8); +/* + enc_wrbreg(handle, ENC_ERXRDPTL, PKTMEM_RX_START & 0xff); + enc_wrbreg(handle, ENC_ERXRDPTH, PKTMEM_RX_START >> 8); +*/ + + /* Set the receive buffer end. */ + + enc_wrbreg(handle, ENC_ERXNDL, PKTMEM_RX_END & 0xff); + enc_wrbreg(handle, ENC_ERXNDH, PKTMEM_RX_END >> 8); + + /* Set transmit buffer start. */ + + handle->transmitLength = 0; + enc_wrbreg(handle, ENC_ETXSTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(handle, ENC_ETXSTH, PKTMEM_TX_START >> 8); + + /* Set filter mode: unicast OR broadcast AND crc valid */ + + enc_wrbreg(handle, ENC_ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN); + + do { + HAL_Delay(10); /* Wait for 10 ms to let the clock be ready */ + regval = enc_rdbreg(handle, ENC_ESTAT); + } while ((regval & ESTAT_CLKRDY) == 0); + + /* Enable MAC receive */ + + enc_wrbreg(handle, ENC_MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS); + + /* Enable automatic padding and CRC operations */ + + if (handle->Init.DuplexMode == ETH_MODE_HALFDUPLEX) { + enc_wrbreg(handle, ENC_MACON3, + ((handle->Init.ChecksumMode == ETH_CHECKSUM_BY_HARDWARE)?MACON3_PADCFG0 | MACON3_TXCRCEN:0) | + MACON3_FRMLNEN); + enc_wrbreg(handle, ENC_MACON4, MACON4_DEFER); /* Defer transmission enable */ + + /* Set Non-Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MAIPGL, 0x12); + enc_wrbreg(handle, ENC_MAIPGH, 0x0c); + + /* Set Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MABBIPG, 0x12); + } else { + /* Set filter mode: unicast OR broadcast AND crc valid AND Full Duplex */ + + enc_wrbreg(handle, ENC_MACON3, + ((handle->Init.ChecksumMode == ETH_CHECKSUM_BY_HARDWARE)?MACON3_PADCFG0 | MACON3_TXCRCEN:0) | + MACON3_FRMLNEN | MACON3_FULDPX); + + /* Set Non-Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MAIPGL, 0x12); + + /* Set Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MABBIPG, 0x15); + } + + /* Set the maximum packet size which the controller will accept */ + + enc_wrbreg(handle, ENC_MAMXFLL, (CONFIG_NET_ETH_MTU+18) & 0xff); + enc_wrbreg(handle, ENC_MAMXFLH, (CONFIG_NET_ETH_MTU+18) >> 8); + + /* Configure LEDs (No, just use the defaults for now) */ + /* enc_wrphy(priv, ENC_PHLCON, ??); */ + + /* Setup up PHCON1 & 2 */ + + if (handle->Init.DuplexMode == ETH_MODE_HALFDUPLEX) { + enc_wrphy(handle, ENC_PHCON1, 0x00); + enc_wrphy(handle, ENC_PHCON2, PHCON2_HDLDIS); /* errata 9 workaround */ + } else { + enc_wrphy(handle, ENC_PHCON1, PHCON1_PDPXMD); /* errata 10 workaround */ + enc_wrphy(handle, ENC_PHCON2, 0x00); + } + + /* Not used Restore normal operation mode + enc_pwrfull(handle); */ + + /* Process interrupt settings */ + if (handle->Init.InterruptEnableBits & EIE_LINKIE) { + /* Enable link change interrupt in PHY module */ + enc_wrphy(handle, ENC_PHIE, PHIE_PGEIE | PHIE_PLNKIE); + } + + /* Since we not modify PHLCON register, we don't fall in errata 11 case */ + + /* Reset all interrupt flags */ + enc_bfcgreg(ENC_EIR, EIR_ALLINTS); + + regval = handle->Init.InterruptEnableBits; + if (regval) { + /* Ensure INTIE is set when at least an interruption is selected */ + regval |= EIE_INTIE; + } + /* Enable selected interrupts in ethernet controller module */ + enc_bfsgreg(ENC_EIE, regval); + + /* Enable the receiver */ + enc_bfsgreg(ENC_ECON1, ECON1_RXEN); + + return true; +} + +/** + * @} + */ + +/**************************************************************************** + * Function: ENC_SetMacAddr + * + * Description: + * Set the MAC address to the configured value. This is done after ifup + * or after a TX timeout. Note that this means that the interface must + * be down before configuring the MAC addr. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_SetMacAddr(ENC_HandleTypeDef *handle) +{ + /* Program the hardware with it's MAC address (for filtering). + * MAADR1 MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 + * MAADR2 MAC Address Byte 2 (MAADR<39:32>), OUI Byte 2 + * MAADR3 MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 + * MAADR4 MAC Address Byte 4 (MAADR<23:16>) + * MAADR5 MAC Address Byte 5 (MAADR<15:8>) + * MAADR6 MAC Address Byte 6 (MAADR<7:0>) + */ + + enc_wrbreg(handle, ENC_MAADR1, handle->Init.MACAddr[0]); + enc_wrbreg(handle, ENC_MAADR2, handle->Init.MACAddr[1]); + enc_wrbreg(handle, ENC_MAADR3, handle->Init.MACAddr[2]); + enc_wrbreg(handle, ENC_MAADR4, handle->Init.MACAddr[3]); + enc_wrbreg(handle, ENC_MAADR5, handle->Init.MACAddr[4]); + enc_wrbreg(handle, ENC_MAADR6, handle->Init.MACAddr[5]); +} + + +/**************************************************************************** + * Function: ENC_WriteBuffer + * + * Description: + * Write a buffer of data. + * + * Parameters: + * buffer - A pointer to the buffer to write from + * buflen - The number of bytes to write + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +void ENC_WriteBuffer(void *buffer, uint16_t buflen) +{ + /* Send the WBM command and copy the packet itself into the transmit + * buffer at the position of the EWRPT register. + */ + + /* Select ENC28J60 chip + * + * "The WBM command is started by lowering the CS pin. ..." + * We explicitly select the ENC28J60 chip because we have to transmits several pieces of + * information while keeping CS low + * + */ + + ENC_SPI_Select(true); + + /* Send the write buffer memory command (ignoring the response) + * + * "...The [3-bit]WBM opcode should then be sent to the ENC28J60, + * followed by the 5-bit constant, 1Ah." + */ + + + ENC_SPI_SendWithoutSelection(ENC_WBM); + + /* Send the buffer + * + * "... After the WBM command and constant are sent, the data to + * be stored in the memory pointed to by EWRPT should be shifted + * out MSb first to the ENC28J60. After 8 data bits are received, + * the Write Pointer will automatically increment if AUTOINC is + * set. The host controller can continue to provide clocks on the + * SCK pin and send data on the SI pin, without raising CS, to + * keep writing to the memory. In this manner, with AUTOINC + * enabled, it is possible to continuously write sequential bytes + * to the buffer memory without any extra SPI command + * overhead. + */ + + ENC_SPI_SendBuf(buffer, NULL, buflen); + + /* De-select ENC28J60 chip + * + * "The WBM command is terminated by bringing up the CS pin. ..." + * done in ENC_SPI_SendBuf callback + */ + +} + +/**************************************************************************** + * Function: enc_rdbuffer + * + * Description: + * Read a buffer of data. + * + * Parameters: + * buffer - A pointer to the buffer to read into + * buflen - The number of bytes to read + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +static void enc_rdbuffer(void *buffer, int16_t buflen) +{ + /* Select ENC28J60 chip */ + + ENC_SPI_Select(true); + + /* Send the read buffer memory command (ignoring the response) */ + + ENC_SPI_SendWithoutSelection(ENC_RBM); + + /* Then read the buffer data */ + + ENC_SPI_SendBuf(NULL, buffer, buflen); + + /* De-select ENC28J60 chip: done in ENC_SPI_SendBuf callback */ +} + +/**************************************************************************** + * Function: ENC_RestoreTXBuffer + * + * Description: + * Prepare TX buffer + * + * Parameters: + * handle - Reference to the driver state structure + * len - length of buffer + * + * Returned Value: + * ERR_OK 0 No error, everything OK. + * ERR_MEM -1 Out of memory error. + * ERR_TIMEOUT -3 Timeout. + * + * Assumptions: + * + ****************************************************************************/ + +int8_t ENC_RestoreTXBuffer(ENC_HandleTypeDef *handle, uint16_t len) +{ + uint16_t txend; + uint8_t control_write[2]; + + /* Wait while TX is busy */ + if (!enc_waitgreg(ENC_ECON1, ECON1_TXRTS, 0)) { + return ERR_TIMEOUT; + } + + /* Verify that the hardware is ready to send another packet. The driver + * starts a transmission process by setting ECON1.TXRTS. When the packet is + * finished transmitting or is aborted due to an error/cancellation, the + * ECON1.TXRTS bit will be cleared. + * + * NOTE: If we got here, then we have committed to sending a packet. + * higher level logic must have assured that TX-related interrupts are disabled. + */ + + /* Send the packet: address=priv->dev.d_buf, length=priv->dev.d_len */ + + /* Set transmit buffer start (is this necessary?). */ + + enc_wrbreg(handle, ENC_ETXSTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(handle, ENC_ETXSTH, PKTMEM_TX_START >> 8); + + /* Reset the write pointer to start of transmit buffer */ + + enc_wrbreg(handle, ENC_EWRPTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(handle, ENC_EWRPTH, PKTMEM_TX_START >> 8); + + /* Set the TX End pointer based on the size of the packet to send. Note + * that the offset accounts for the control byte at the beginning the + * buffer plus the size of the packet data. + */ + + txend = PKTMEM_TX_START + len; + + if (txend+8>PKTMEM_TX_ENDP1) { + return ERR_MEM; + } + + enc_wrbreg(handle, ENC_ETXNDL, txend & 0xff); + enc_wrbreg(handle, ENC_ETXNDH, txend >> 8); + + /* Send the write buffer memory command (ignoring the response) + * + * "...The [3-bit]WBM opcode should then be sent to the ENC28J60, + * followed by the 5-bit constant, 1Ah." + * + * "...the ENC28J60 requires a single per packet control byte to + * precede the packet for transmission." + * + * POVERRIDE: Per Packet Override bit (Not set): + * 1 = The values of PCRCEN, PPADEN and PHUGEEN will override the + * configuration defined by MACON3. + * 0 = The values in MACON3 will be used to determine how the packet + * will be transmitted + * PCRCEN: Per Packet CRC Enable bit (Set, but won't be used because + * POVERRIDE is zero). + * PPADEN: Per Packet Padding Enable bit (Set, but won't be used because + * POVERRIDE is zero). + * PHUGEEN: Per Packet Huge Frame Enable bit (Set, but won't be used + * because POVERRIDE is zero). + */ + + control_write[0] = ENC_WBM; + control_write[1] = PKTCTRL_PCRCEN | PKTCTRL_PPADEN | PKTCTRL_PHUGEEN; + ENC_SPI_SendBuf(control_write, control_write, 2); + + return ERR_OK; +} + +/**************************************************************************** + * Function: ENC_Transmit + * + * Description: + * Start hardware transmission. Called either from: + * + * - pkif interrupt when an application responds to the receipt of data + * by trying to send something, or + * - From watchdog based polling. + * + * Parameters: + * handle - Reference to the driver state structure + * len - length of buffer + * + * Returned Value: + * none + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef USE_PROTOTHREADS +PT_THREAD(ENC_Transmit(struct pt *pt, ENC_HandleTypeDef *handle)) +#else +void ENC_Transmit(ENC_HandleTypeDef *handle) +#endif +{ + PT_BEGIN(pt); + + if (handle->transmitLength != 0) { + /* A frame is ready for transmission */ + /* Set TXRTS to send the packet in the transmit buffer */ + + //enc_bfsgreg(ENC_ECON1, ECON1_TXRTS); + /* Implement erratas 12, 13 and 15 */ + /* Reset transmit logic */ + handle->retries = 16; + do { + enc_bfsgreg(ENC_ECON1, ECON1_TXRST); + enc_bfcgreg(ENC_ECON1, ECON1_TXRST); + enc_bfcgreg(ENC_EIR, EIR_TXERIF | EIR_TXIF); + + /* Start transmission */ + enc_bfsgreg(ENC_ECON1, ECON1_TXRTS); + +#ifdef USE_PROTOTHREADS + handle->startTime = HAL_GetTick(); + handle->duration = 20; /* Timeout after 20 ms */ + PT_WAIT_UNTIL(pt, (((enc_rdgreg(ENC_EIR) & (EIR_TXIF | EIR_TXERIF)) != 0) || + (HAL_GetTick() - handle->startTime > handle->duration))); +#else + /* Wait for end of transmission */ + enc_waitwhilegreg(ENC_EIR, EIR_TXIF | EIR_TXERIF, 0); +#endif + + HAL_Delay(20); // Added by AGB - fixes weird timing bug + + /* Stop transmission */ + enc_bfcgreg(ENC_ECON1, ECON1_TXRTS); + + { + uint16_t addtTsv4; + uint8_t tsv4, regval; + + /* read tsv */ + addtTsv4 = PKTMEM_TX_START + handle->transmitLength + 4; + + enc_wrbreg(handle, ENC_ERDPTL, addtTsv4 & 0xff); + enc_wrbreg(handle, ENC_ERDPTH, addtTsv4 >> 8); + + enc_rdbuffer(&tsv4, 1); + + regval = enc_rdgreg(ENC_EIR); + if (!(regval & EIR_TXERIF) || !(tsv4 & TSV_LATECOL)) { + break; + } + } + handle->retries--; + } while (handle->retries > 0); + /* Transmission finished (but can be unsuccessful) */ + handle->transmitLength = 0; + } + PT_END(pt); +} + +/**************************************************************************** + * Function: ENC_GetReceivedFrame + * + * Description: + * Check if we have received packet, and if so, retrieve them. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * true if new packet is available; false otherwise + * + * Assumptions: + * + ****************************************************************************/ + +bool ENC_GetReceivedFrame(ENC_HandleTypeDef *handle) +{ + uint8_t rsv[6]; + uint16_t pktlen; + uint16_t rxstat; + + uint8_t pktcnt; + + bool result = true; + pktcnt = enc_rdbreg(handle, ENC_EPKTCNT); + if (pktcnt == 0) { + return false; + }; + + /* Set the read pointer to the start of the received packet (ERDPT) */ + + enc_wrbreg(handle, ENC_ERDPTL, (handle->nextpkt) & 0xff); + enc_wrbreg(handle, ENC_ERDPTH, (handle->nextpkt) >> 8); + + /* Read the next packet pointer and the 4 byte read status vector (RSV) + * at the beginning of the received packet. (ERDPT should auto-increment + * and wrap to the beginning of the read buffer as necessary) + */ + + enc_rdbuffer(rsv, 6); + + /* Decode the new next packet pointer, and the RSV. The + * RSV is encoded as: + * + * Bits 0-15: Indicates length of the received frame. This includes the + * destination address, source address, type/length, data, + * padding and CRC fields. This field is stored in little- + * endian format. + * Bits 16-31: Bit encoded RX status. + */ + + handle->nextpkt = (uint16_t)rsv[1] << 8 | (uint16_t)rsv[0]; + pktlen = (uint16_t)rsv[3] << 8 | (uint16_t)rsv[2]; + rxstat = (uint16_t)rsv[5] << 8 | (uint16_t)rsv[4]; + + /* Check if the packet was received OK */ + + if ((rxstat & RXSTAT_OK) == 0) { +#ifdef CONFIG_ENC28J60_STATS + priv->stats.rxnotok++; +#endif + result = false; + } else { /* Check for a usable packet length (4 added for the CRC) */ + if (pktlen > (CONFIG_NET_ETH_MTU + 4) || pktlen <= (ETH_HDRLEN + 4)) { + #ifdef CONFIG_ENC28J60_STATS + priv->stats.rxpktlen++; + #endif + result = false; + } else { /* Otherwise, read and process the packet */ + /* Save the packet length (without the 4 byte CRC) in handle->RxFrameInfos.length*/ + + handle->RxFrameInfos.length = pktlen - 4; + + /* Copy the data data from the receive buffer to priv->dev.d_buf. + * ERDPT should be correctly positioned from the last call to to + * end_rdbuffer (above). + */ + + enc_rdbuffer(handle->RxFrameInfos.buffer, handle->RxFrameInfos.length); + + } + } + + /* Move the RX read pointer to the start of the next received packet. + * This frees the memory we just read. + */ + + /* Errata 14 (on se sert de rxstat comme variable temporaire */ + rxstat = handle->nextpkt; + if (rxstat == PKTMEM_RX_START) { + rxstat = PKTMEM_RX_END; + } else { + rxstat--; + } + enc_wrbreg(handle, ENC_ERXRDPTL, rxstat & 0xff); + enc_wrbreg(handle, ENC_ERXRDPTH, rxstat >> 8); +/* + enc_wrbreg(handle, ENC_ERXRDPTL, (handle->nextpkt)); + enc_wrbreg(handle, ENC_ERXRDPTH, (handle->nextpkt) >> 8); +*/ + + /* Decrement the packet counter indicate we are done with this packet */ + + enc_bfsgreg(ENC_ECON2, ECON2_PKTDEC); + + return result; +} + +/**************************************************************************** + * Function: enc_linkstatus + * + * Description: + * The current link status can be obtained from the PHSTAT1.LLSTAT or + * PHSTAT2.LSTAT. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_linkstatus(ENC_HandleTypeDef *handle) +{ + handle->LinkStatus = enc_rdphy(handle, ENC_PHSTAT2); +} + +/**************************************************************************** + * Function: ENC_EnableInterrupts + * + * Description: + * Enable individual ENC28J60 interrupts + * + * Parameters: + * bits - The individual bits to enable + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_EnableInterrupts(uint8_t bits) +{ + enc_bfsgreg(ENC_EIE, bits); +} + + +/**************************************************************************** + * Function: ENC_IRQHandler + * + * Description: + * Perform interrupt handling logic outside of the interrupt handler (on + * the work queue thread). + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_IRQHandler(ENC_HandleTypeDef *handle) +{ + uint8_t eir; + + /* Disable further interrupts by clearing the global interrupt enable bit. + * "After an interrupt occurs, the host controller should clear the global + * enable bit for the interrupt pin before servicing the interrupt. Clearing + * the enable bit will cause the interrupt pin to return to the non-asserted + * state (high). Doing so will prevent the host controller from missing a + * falling edge should another interrupt occur while the immediate interrupt + * is being serviced." + */ + + enc_bfcgreg(ENC_EIE, EIE_INTIE); + + /* Read EIR for interrupt flags + */ + + eir = enc_rdgreg(ENC_EIR) & EIR_ALLINTS; + + /* PKTIF is not reliable, check PKCNT instead */ + if (enc_rdbreg(handle, ENC_EPKTCNT) != 0) { + /* Manage EIR_PKTIF by software */ + eir |= EIR_PKTIF; + } + + /* Store interrupt flags in handle */ + handle->interruptFlags = eir; + + /* If link status has changed, read it */ + if ((eir & EIR_LINKIF) != 0) /* Link change interrupt */ + { + enc_linkstatus(handle); /* Get current link status */ + enc_rdphy(handle, ENC_PHIR); /* Clear the LINKIF interrupt */ + } + + /* Reset ENC28J60 interrupt flags, except PKTIF form which interruption is deasserted when PKTCNT reaches 0 */ + enc_bfcgreg(ENC_EIR, EIR_ALLINTS); + + /* Enable Ethernet interrupts */ + /* done after effective process on interrupts enc_bfsgreg(ENC_EIE, EIE_INTIE); */ +} + +/**************************************************************************** + * Function: ENC_GetPkcnt + * + * Description: + * Get the number of pending receive packets + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * the number of receive packet not processed yet + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_GetPkcnt(ENC_HandleTypeDef *handle) +{ + handle->pktCnt = enc_rdbreg(handle, ENC_EPKTCNT); +} + + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/part14-spi-ethernet-interrupt/net/enc28j60.h b/part14-spi-ethernet-interrupt/net/enc28j60.h new file mode 100644 index 0000000..695095c --- /dev/null +++ b/part14-spi-ethernet-interrupt/net/enc28j60.h @@ -0,0 +1,816 @@ +/** + ****************************************************************************** + * @file enc28j60.h + * @author Christian Schoffit, portions from Gregory Nutt: + * Copyright (C) 2010-2012, 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * @version V1.0.0 + * @date 02-June-2015 + * @brief This file provides a set of functions needed to manage the ENC28J60 + * Stand-Alone Ethernet Controller with SPI Interface. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2015 Christian Schoffit

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Christian Schoffit nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +#ifndef ENC28J60_H_INCLUDED +#define ENC28J60_H_INCLUDED + +#include +#include + +#ifdef USE_PROTOTHREADS +#include "pt.h" +#else +#define PT_BEGIN(x) +#define PT_END(x) +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +/* Ethernet frames are between 64 and 1518 bytes long */ + +#define MIN_FRAMELEN 64 +#define MAX_FRAMELEN 1518 + + +/* External functions --------------------------------------------------------*/ +void HAL_Delay(volatile uint32_t Delay); +uint32_t HAL_GetTick(void); + +/* Callback functions *********************************************************/ + +/** + * Implement SPI Slave selection and deselection. Must be provided by user code + * param select: true if the ENC28J60 slave SPI if selected, false otherwise + * retval none + */ + +void ENC_SPI_Select(bool select); + +/** + * Implement SPI single byte send and receive. + * The ENC28J60 slave SPI must already be selected and wont be deselected after transmission + * Must be provided by user code + * param command: command or data to be sent to ENC28J60 + * retval answer from ENC28J60 + */ + +void ENC_SPI_SendWithoutSelection(uint8_t command); + +/** + * Implement SPI single byte send and receive. Must be provided by user code + * param command: command or data to be sent to ENC28J60 + * retval answer from ENC28J60 + */ + +void ENC_SPI_Send(uint8_t command); + +/** + * Implement SPI buffer send and receive. Must be provided by user code + * param master2slave: data to be sent from host to ENC28J60, can be NULL if we only want to receive data from slave + * param slave2master: answer from ENC28J60 to host, can be NULL if we only want to send data to slave + * retval none + */ + +void ENC_SPI_SendBuf(uint8_t *master2slave, uint8_t *slave2master, uint16_t bufferSize); + +/* Exported types ------------------------------------------------------------*/ +/** @defgroup ETH_Exported_Types ETH Exported Types + * @{ + */ + +/** + * @brief ETH Init Structure definition + */ + +typedef struct +{ + uint32_t DuplexMode; /*!< Selects the MAC duplex mode: Half-Duplex or Full-Duplex mode + This parameter can be a value of @ref ETH_Duplex_Mode */ + + uint8_t *MACAddr; /*!< MAC Address of used Hardware: must be pointer on an array of 6 bytes */ + + uint32_t ChecksumMode; /*!< Selects if the checksum is check by hardware or by software. + This parameter can be a value of @ref ETH_Checksum_Mode */ + + uint8_t InterruptEnableBits; /*!< Selects the enabled interrupts */ +} ENC_InitTypeDef; + + +/** + * @brief Received Frame Informations structure definition + */ +typedef struct +{ + uint32_t length; /*!< Frame length */ + + uint8_t buffer[MAX_FRAMELEN+20]; /*!< Frame buffer */ + +} ENC_RxFrameInfos; + + +/** + * @brief ENC28J60 Handle Structure definition + */ + +typedef struct +{ + ENC_InitTypeDef Init; /*!< Ethernet Init Configuration */ + + uint8_t bank; /*!< Currently selected bank */ + uint8_t interruptFlags;/*!< The last value of interrupts flags */ + uint8_t pktCnt; /*!< The number of pending receive packets */ + uint16_t nextpkt; /*!< Next packet address */ + uint16_t LinkStatus; /*!< Ethernet link status */ + uint16_t transmitLength;/*!< The length of ip frame to transmit */ + uint32_t startTime; /*!< The start time of the current timer */ + uint32_t duration; /*!< The duration of the current timer in ms */ + uint16_t retries; /*!< The number of transmission retries left to do */ + + ENC_RxFrameInfos RxFrameInfos; /*!< last Rx frame infos */ +} ENC_HandleTypeDef; + + /** + * @} + */ + +/* Exported constants --------------------------------------------------------*/ +/** @defgroup ETH_Exported_Constants ETH Exported Constants + * @{ + */ +/* Size of the Ethernet header */ +#define ETH_HDRLEN 14 /* Minimum size: 2*6 + 2 */ + + +#define ERR_OK 0 /* No error, everything OK. */ +#define ERR_MEM -1 /* Out of memory error. */ +#define ERR_BUF -2 /* Buffer error. */ +#define ERR_TIMEOUT -3 /* Timeout. */ + +/* ENC28J60 Commands ********************************************************/ +/* A total of seven instructions are implemented on the ENC28J60. Where: + * + * aaaaaa is the 5-bit address of a control register, and + * dddddddd is one or more bytes of data that may accompany the command. + */ + +#define ENC_RCR (0x00) /* Read Control Register + * 000 | aaaaa | (Register value returned)) */ +#define ENC_RBM (0x3a) /* Read Buffer Memory + * 001 | 11010 | (Read buffer data follows) */ +#define ENC_WCR (0x40) /* Write Control Register + * 010 | aaaaa | dddddddd */ +#define ENC_WBM (0x7a) /* Write Buffer Memory + * 011 | 11010 | (Write buffer data follows) */ +#define ENC_BFS (0x80) /* Bit Field Set + * 100 | aaaaa | dddddddd */ +#define ENC_BFC (0xa0) /* Bit Field Clear + * 101 | aaaaa | dddddddd */ +#define ENC_SRC (0xff) /* System Reset + * 111 | 11111 | (No data) */ + +/* Global Control Registers *************************************************/ +/* Control registers are accessed with the RCR, RBM, WCR, BFS, and BFC + * commands. The following identifies all ENC28J60 control registers. The + * control register memory is partitioned into four banks, selectable by the + * bank select bits, BSEL1:BSEL0, in the ECON1 register. + * + * The last five locations (0x1b to 0x1f) of all banks point to a common set + * of registers: EIE, EIR, ESTAT, ECON2 and ECON1. These are key registers + * used in controlling and monitoring the operation of the device. Their + * common mapping allows easy access without switching the bank. + * + * Control registers for the ENC28J60 are generically grouped as ETH, MAC and + * MII registers. Register names starting with E belong to the ETH group. + * Similarly, registers names starting with MA belong to the MAC group and + * registers prefixed with MI belong to the MII group. + */ + +#define ENC_EIE (0x1b) /* Ethernet Interrupt Enable Register */ +#define ENC_EIR (0x1c) /* Ethernet Interupt Request Register */ +#define ENC_ESTAT (0x1d) /* Ethernet Status Register */ +#define ENC_ECON2 (0x1e) /* Ethernet Control 2 Register */ +#define ENC_ECON1 (0x1f) /* Ethernet Control 1 Register */ + +/* Ethernet Interrupt Enable Register Bit Definitions */ + +#define EIE_RXERIE (1 << 0) /* Bit 0: Receive Error Interrupt Enable */ +#define EIE_TXERIE (1 << 1) /* Bit 1: Transmit Error Interrupt Enable */ + /* Bit 2: Reserved */ +#define EIE_TXIE (1 << 3) /* Bit 3: Transmit Enable */ +#define EIE_LINKIE (1 << 4) /* Bit 4: Link Status Change Interrupt Enable */ +#define EIE_DMAIE (1 << 5) /* Bit 5: DMA Interrupt Enable */ +#define EIE_PKTIE (1 << 6) /* Bit 6: Receive Packet Pending Interrupt Enable */ +#define EIE_INTIE (1 << 7) /* Bit 7: Global INT Interrupt Enable */ + +/* Ethernet Interrupt Request Register Bit Definitions */ + +#define EIR_RXERIF (1 << 0) /* Bit 0: Receive Error Interrupt */ +#define EIR_TXERIF (1 << 1) /* Bit 1: Transmit Error Interrupt */ + /* Bit 2: Reserved */ +#define EIR_TXIF (1 << 3) /* Bit 3: Transmit Interrupt */ +#define EIR_LINKIF (1 << 4) /* Bit 4: Link Change Interrupt */ +#define EIR_DMAIF (1 << 5) /* Bit 5: DMA Interrupt */ +#define EIR_PKTIF (1 << 6) /* Bit 6: Receive Packet Pending Interrupt */ + /* Bit 7: Reserved */ +#define EIR_ALLINTS (EIR_RXERIF | EIR_TXERIF | EIR_TXIF | EIR_LINKIF | EIR_DMAIF) /* All interrupts bar EIR_PKTIF*/ + +/* Ethernet Status Register Bit Definitions */ + +#define ESTAT_CLKRDY (1 << 0) /* Bit 0: Clock Ready */ +#define ESTAT_TXABRT (1 << 1) /* Bit 1: Transmit Abort Error */ +#define ESTAT_RXBUSY (1 << 2) /* Bit 2: Receive Busy */ + /* Bit 3: Reserved */ +#define ESTAT_LATECOL (1 << 4) /* Bit 4: Late Collision Error */ + /* Bit 5: Reserved */ +#define ESTAT_BUFER (1 << 6) /* Bit 6: Ethernet Buffer Error Status */ +#define ESTAT_INT (1 << 7) /* Bit 7: INT Interrupt */ + +/* Ethernet Control 1 Register Bit Definitions */ + +#define ECON1_BSEL_SHIFT (0) /* Bits 0-1: Bank select */ +#define ECON1_BSEL_MASK (3 << ECON1_BSEL_SHIFT) +# define ECON1_BSEL_BANK0 (0 << ECON1_BSEL_SHIFT) /* Bank 0 */ +# define ECON1_BSEL_BANK1 (1 << ECON1_BSEL_SHIFT) /* Bank 1 */ +# define ECON1_BSEL_BANK2 (2 << ECON1_BSEL_SHIFT) /* Bank 2 */ +# define ECON1_BSEL_BANK3 (3 << ECON1_BSEL_SHIFT) /* Bank 3 */ +#define ECON1_RXEN (1 << 2) /* Bit 2: Receive Enable */ +#define ECON1_TXRTS (1 << 3) /* Bit 3: Transmit Request to Send */ +#define ECON1_CSUMEN (1 << 4) /* Bit 4: DMA Checksum Enable */ +#define ECON1_DMAST (1 << 5) /* Bit 5: DMA Start and Busy Status */ +#define ECON1_RXRST (1 << 6) /* Bit 6: Receive Logic Reset */ +#define ECON1_TXRST (1 << 7) /* Bit 7: Transmit Logic Reset */ + +/* Ethernet Control 2 Register */ + /* Bits 0-2: Reserved */ +#define ECON2_VRPS (1 << 3) /* Bit 3: Voltage Regulator Power Save Enable */ + /* Bit 4: Reserved */ +#define ECON2_PWRSV (1 << 5) /* Bit 5: Power Save Enable */ +#define ECON2_PKTDEC (1 << 6) /* Bit 6: Packet Decrement */ +#define ECON2_AUTOINC (1 << 7) /* Bit 7: Automatic Buffer Pointer Increment Enable */ + +/* Banked Control Registers *************************************************/ +/* The remaining control registers are identified with a a 5 bit address and + * a bank selection. We pack the bank number and an indication if this is + * a MAC/PHY register access together with the control register address + * together to keep the design simpler. + */ + +#define ENC_ADDR_SHIFT (0) /* Bits 0-4: Register address */ +#define ENC_ADDR_MASK (0x1f << ENC_ADDR_SHIFT) +#define ENC_BANK_SHIFT (5) /* Bits 5-6: Bank number */ +#define ENC_BANK_MASK (3 << ENC_BSEL_SHIFT) +# define ENC_BANK0 (0 << ENC_BSEL_SHIFT) +# define ENC_BANK1 (1 << ENC_BSEL_SHIFT) +# define ENC_BANK2 (2 << ENC_BSEL_SHIFT) +# define ENC_BANK3 (3 << ENC_BSEL_SHIFT) +#define ENC_PHYMAC_SHIFT (7) /* Bit 7: This is a PHY/MAC command */ +#define ENC_PHYMAC (1 << ENC_PHYMAC_SHIFT) + +#define REGADDR(a,b,m) ((m) << ENC_PHYMAC_SHIFT | (b) << ENC_BANK_SHIFT | (a)) +#define GETADDR(a) ((a) & ENC_ADDR_MASK) +#define GETBANK(a) (((a) >> ENC_BANK_SHIFT) & 3) +#define ISPHYMAC(a) (((a) & ENC_PHYMAC) != 0) + +/* Bank 0 Control Register Addresses */ + +#define ENC_ERDPTL REGADDR(0x00, 0, 0) /* Read Pointer Low Byte (ERDPT<7:0> */ +#define ENC_ERDPTH REGADDR(0x01, 0, 0) /* Read Pointer High Byte (ERDPT<12:8>) */ +#define ENC_EWRPTL REGADDR(0x02, 0, 0) /* Write Pointer Low Byte (EWRPT<7:0>) */ +#define ENC_EWRPTH REGADDR(0x03, 0, 0) /* Write Pointer High Byte (EWRPT<12:8>) */ +#define ENC_ETXSTL REGADDR(0x04, 0, 0) /* TX Start Low Byte (ETXST<7:0>) */ +#define ENC_ETXSTH REGADDR(0x05, 0, 0) /* TX Start High Byte (ETXST<12:8>) */ +#define ENC_ETXNDL REGADDR(0x06, 0, 0) /* TX End Low Byte (ETXND<7:0>) */ +#define ENC_ETXNDH REGADDR(0x07, 0, 0) /* TX End High Byte (ETXND<12:8>) */ +#define ENC_ERXSTL REGADDR(0x08, 0, 0) /* RX Start Low Byte (ERXST<7:0>) */ +#define ENC_ERXSTH REGADDR(0x09, 0, 0) /* RX Start High Byte (ERXST<12:8>) */ +#define ENC_ERXNDL REGADDR(0x0a, 0, 0) /* RX End Low Byte (ERXND<7:0>) */ +#define ENC_ERXNDH REGADDR(0x0b, 0, 0) /* RX End High Byte (ERXND<12:8>) */ +#define ENC_ERXRDPTL REGADDR(0x0c, 0, 0) /* RX RD Pointer Low Byte (ERXRDPT<7:0>) */ +#define ENC_ERXRDPTH REGADDR(0x0d, 0, 0) /* RX RD Pointer High Byte (ERXRDPT<12:8>) */ +#define ENC_ERXWRPTL REGADDR(0x0e, 0, 0) /* RX WR Pointer Low Byte (ERXWRPT<7:0>) */ +#define ENC_ERXWRPTH REGADDR(0x0f, 0, 0) /* RX WR Pointer High Byte (ERXWRPT<12:8>) */ +#define ENC_EDMASTL REGADDR(0x10, 0, 0) /* DMA Start Low Byte (EDMAST<7:0>) */ +#define ENC_EDMASTH REGADDR(0x11, 0, 0) /* DMA Start High Byte (EDMAST<12:8>) */ +#define ENC_EDMANDL REGADDR(0x12, 0, 0) /* DMA End Low Byte (EDMAND<7:0>) */ +#define ENC_EDMANDH REGADDR(0x13, 0, 0) /* DMA End High Byte (EDMAND<12:8>) */ +#define ENC_EDMADSTL REGADDR(0x14, 0, 0) /* DMA Destination Low Byte (EDMADST<7:0>) */ +#define ENC_EDMADSTH REGADDR(0x15, 0, 0) /* DMA Destination High Byte (EDMADST<12:8>) */ +#define ENC_EDMACSL REGADDR(0x16, 0, 0) /* DMA Checksum Low Byte (EDMACS<7:0>) */ +#define ENC_EDMACSH REGADDR(0x17, 0, 0) /* DMA Checksum High Byte (EDMACS<15:8>) */ + /* 0x18-0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ +/* Bank 1 Control Register Addresses */ + +#define ENC_EHT0 REGADDR(0x00, 1, 0) /* Hash Table Byte 0 (EHT<7:0>) */ +#define ENC_EHT1 REGADDR(0x01, 1, 0) /* Hash Table Byte 1 (EHT<15:8>) */ +#define ENC_EHT2 REGADDR(0x02, 1, 0) /* Hash Table Byte 2 (EHT<23:16>) */ +#define ENC_EHT3 REGADDR(0x03, 1, 0) /* Hash Table Byte 3 (EHT<31:24>) */ +#define ENC_EHT4 REGADDR(0x04, 1, 0) /* Hash Table Byte 4 (EHT<39:32>) */ +#define ENC_EHT5 REGADDR(0x05, 1, 0) /* Hash Table Byte 5 (EHT<47:40>) */ +#define ENC_EHT6 REGADDR(0x06, 1, 0) /* Hash Table Byte 6 (EHT<55:48>) */ +#define ENC_EHT7 REGADDR(0x07, 1, 0) /* Hash Table Byte 7 (EHT<63:56>) */ +#define ENC_EPMM0 REGADDR(0x08, 1, 0) /* Pattern Match Mask Byte 0 (EPMM<7:0>) */ +#define ENC_EPMM1 REGADDR(0x09, 1, 0) /* Pattern Match Mask Byte 1 (EPMM<15:8>) */ +#define ENC_EPMM2 REGADDR(0x0a, 1, 0) /* Pattern Match Mask Byte 2 (EPMM<23:16>) */ +#define ENC_EPMM3 REGADDR(0x0b, 1, 0) /* Pattern Match Mask Byte 3 (EPMM<31:24>) */ +#define ENC_EPMM4 REGADDR(0x0c, 1, 0) /* Pattern Match Mask Byte 4 (EPMM<39:32>) */ +#define ENC_EPMM5 REGADDR(0x0d, 1, 0) /* Pattern Match Mask Byte 5 (EPMM<47:40>) */ +#define ENC_EPMM6 REGADDR(0x0e, 1, 0) /* Pattern Match Mask Byte 6 (EPMM<55:48>) */ +#define ENC_EPMM7 REGADDR(0x0f, 1, 0) /* Pattern Match Mask Byte 7 (EPMM<63:56>) */ +#define ENC_EPMCSL REGADDR(0x10, 1, 0) /* Pattern Match Checksum Low Byte (EPMCS<7:0>) */ +#define ENC_EPMCSH REGADDR(0x11, 1, 0) /* Pattern Match Checksum High Byte (EPMCS<15:0>) */ + /* 0x12-0x13: Reserved */ +#define ENC_EPMOL REGADDR(0x14, 1, 0) /* Pattern Match Offset Low Byte (EPMO<7:0>) */ +#define ENC_EPMOH REGADDR(0x15, 1, 0) /* Pattern Match Offset High Byte (EPMO<12:8>) */ + /* 0x16-0x17: Reserved */ +#define ENC_ERXFCON REGADDR(0x18, 1, 0) /* Receive Filter Configuration */ +#define ENC_EPKTCNT REGADDR(0x19, 1, 0) /* Ethernet Packet Count */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* Receive Filter Configuration Bit Definitions */ + +#define ERXFCON_BCEN (1 << 0) /* Bit 0: Broadcast Filter Enable */ +#define ERXFCON_MCEN (1 << 1) /* Bit 1: Multicast Filter Enable */ +#define ERXFCON_HTEN (1 << 2) /* Bit 2: Hash Table Filter Enable */ +#define ERXFCON_MPEN (1 << 3) /* Bit 3: Magic Packet Filter Enable */ +#define ERXFCON_PMEN (1 << 4) /* Bit 4: Pattern Match Filter Enable */ +#define ERXFCON_CRCEN (1 << 5) /* Bit 5: Post-Filter CRC Check Enable */ +#define ERXFCON_ANDOR (1 << 6) /* Bit 6: AND/OR Filter Select */ +#define ERXFCON_UCEN (1 << 7) /* Bit 7: Unicast Filter Enable */ + +/* Bank 2 Control Register Addresses */ + +#define ENC_MACON1 REGADDR(0x00, 2, 1) /* MAC Control 1 */ + /* 0x01: Reserved */ +#define ENC_MACON3 REGADDR(0x02, 2, 1) /* MAC Control 3 */ +#define ENC_MACON4 REGADDR(0x03, 2, 1) /* MAC Control 4 */ +#define ENC_MABBIPG REGADDR(0x04, 2, 1) /* Back-to-Back Inter-Packet Gap (BBIPG<6:0>) */ + /* 0x05: Reserved */ +#define ENC_MAIPGL REGADDR(0x06, 2, 1) /* Non-Back-to-Back Inter-Packet Gap Low Byte (MAIPGL<6:0>) */ +#define ENC_MAIPGH REGADDR(0x07, 2, 1) /* Non-Back-to-Back Inter-Packet Gap High Byte (MAIPGH<6:0>) */ +#define ENC_MACLCON1 REGADDR(0x08, 2, 1) /* MAC Collision Control 1 */ +#define ENC_MACLCON2 REGADDR(0x09, 2, 1) /* MAC Collision Control 2 */ +#define ENC_MAMXFLL REGADDR(0x0a, 2, 1) /* Maximum Frame Length Low Byte (MAMXFL<7:0>) */ +#define ENC_MAMXFLH REGADDR(0x0b, 2, 1) /* Maximum Frame Length High Byte (MAMXFL<15:8>) */ + /* 0x0c-0x11: Reserved */ +#define ENC_MICMD REGADDR(0x12, 2, 1) /* MII Command Register */ + /* 0x13: Reserved */ +#define ENC_MIREGADR REGADDR(0x14, 2, 1) /* MII Register Address */ + /* 0x15: Reserved */ +#define ENC_MIWRL REGADDR(0x16, 2, 1) /* MII Write Data Low Byte (MIWR<7:0>) */ +#define ENC_MIWRH REGADDR(0x17, 2, 1) /* MII Write Data High Byte (MIWR<15:8>) */ +#define ENC_MIRDL REGADDR(0x18, 2, 1) /* MII Read Data Low Byte (MIRD<7:0>) */ +#define ENC_MIRDH REGADDR(0x19, 2, 1) /* MII Read Data High Byte(MIRD<15:8>) */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON1_MARXEN (1 << 0) /* Bit 0: MAC Receive Enable */ +#define MACON1_PASSALL (1 << 1) /* Bit 1: Pass All Received Frames Enable */ +#define MACON1_RXPAUS (1 << 2) /* Bit 2: Pause Control Frame Reception Enable */ +#define MACON1_TXPAUS (1 << 3) /* Bit 3: Pause Control Frame Transmission Enable */ + /* Bits 4-7: Unimplemented or reserved */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON3_FULDPX (1 << 0) /* Bit 0: MAC Full-Duplex Enable */ +#define MACON3_FRMLNEN (1 << 1) /* Bit 1: Frame Length Checking Enable */ +#define MACON3_HFRMLEN (1 << 2) /* Bit 2: Huge Frame Enable */ +#define MACON3_PHDRLEN (1 << 3) /* Bit 3: Proprietary Header Enable */ +#define MACON3_TXCRCEN (1 << 4) /* Bit 4: Transmit CRC Enable */ +#define MACON3_PADCFG0 (1 << 5) /* Bit 5: Automatic Pad and CRC Configuration */ +#define MACON3_PADCFG1 (1 << 6) /* Bit 6: " " " " " " " " " " */ +#define MACON3_PADCFG2 (1 << 7) /* Bit 7: " " " " " " " " " " */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON4_NOBKOFF (1 << 4) /* Bit 4: No Backoff Enable */ +#define MACON4_BPEN (1 << 5) /* Bit 5: No Backoff During Backpressure Enable */ +#define MACON4_DEFER (1 << 6) /* Bit 6: Defer Transmission Enable bit */ + +/* MII Command Register Bit Definitions */ + +#define MICMD_MIIRD (1 << 0) /* Bit 0: MII Read Enable */ +#define MICMD_MIISCAN (1 << 1) /* Bit 1: MII Scan Enable */ + +/* Bank 3 Control Register Addresses */ + +#define ENC_MAADR5 REGADDR(0x00, 3, 1) /* MAC Address Byte 5 (MAADR<15:8>) */ +#define ENC_MAADR6 REGADDR(0x01, 3, 1) /* MAC Address Byte 6 (MAADR<7:0>) */ +#define ENC_MAADR3 REGADDR(0x02, 3, 1) /* MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 */ +#define ENC_MAADR4 REGADDR(0x03, 3, 1) /* MAC Address Byte 4 (MAADR<23:16>) */ +#define ENC_MAADR1 REGADDR(0x04, 3, 1) /* MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 */ +#define ENC_MAADR2 REGADDR(0x05, 3, 1) /* MAC Address Byte 2 (MAADR<39:32>), OUI Byte 2 */ +#define ENC_EBSTSD REGADDR(0x06, 3, 0) /* Built-in Self-Test Fill Seed (EBSTSD<7:0>) */ +#define ENC_EBSTCON REGADDR(0x07, 3, 0) /* Built-in Self-Test Control */ +#define ENC_EBSTCSL REGADDR(0x08, 3, 0) /* Built-in Self-Test Checksum Low Byte (EBSTCS<7:0>) */ +#define ENC_EBSTCSH REGADDR(0x09, 3, 0) /* Built-in Self-Test Checksum High Byte (EBSTCS<15:8>) */ +#define ENC_MISTAT REGADDR(0x0a, 3, 1) /* MII Status Register */ + /* 0x0b-0x11: Reserved */ +#define ENC_EREVID REGADDR(0x12, 3, 0) /* Ethernet Revision ID */ + /* 0x13-0x14: Reserved */ +#define ENC_ECOCON REGADDR(0x15, 3, 0) /* Clock Output Control */ + /* 0x16: Reserved */ +#define ENC_EFLOCON REGADDR(0x17, 3, 0) /* Ethernet Flow Control */ +#define ENC_EPAUSL REGADDR(0x18, 3, 0) /* Pause Timer Value Low Byte (EPAUS<7:0>) */ +#define ENC_EPAUSH REGADDR(0x19, 3, 0) /* Pause Timer Value High Byte (EPAUS<15:8>) */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* Built-in Self-Test Control Register Bit Definitions */ + +#define EBSTCON_BISTST (1 << 0) /* Bit 0: Built-in Self-Test Start/Busy */ +#define EBSTCON_TME (1 << 1) /* Bit 1: Test Mode Enable */ +#define EBSTCON_TMSEL0 (1 << 2) /* Bit 2: Test Mode Select */ +#define EBSTCON_TMSEL1 (1 << 3) /* Bit 3: " " " " " " */ +#define EBSTCON_PSEL (1 << 4) /* Bit 4: Port Select */ +#define EBSTCON_PSV0 (1 << 5) /* Bit 5: Pattern Shift Value */ +#define EBSTCON_PSV1 (1 << 6) /* Bit 6: " " " " " */ +#define EBSTCON_PSV2 (1 << 7) /* Bit 7: " " " " " */ + +/* MII Status Register Register Bit Definitions */ + +#define MISTAT_BUSY (1 << 0) /* Bit 0: MII Management Busy */ +#define MISTAT_SCAN (1 << 1) /* Bit 1: MII Management Scan Operation */ +#define MISTAT_NVALID (1 << 2) /* Bit 2: MII Management Read Data Not Valid */ + /* Bits 3-7: Reserved or unimplemented */ + +/* Ethernet Flow Control Register Bit Definitions */ + +#define EFLOCON_FCEN0 (1 << 0) /* Bit 0: Flow Control Enable */ +#define EFLOCON_FCEN1 (1 << 1) /* Bit 1: " " " " " " */ +#define EFLOCON_FULDPXS (1 << 2) /* Bit 2: Read-Only MAC Full-Duplex Shadow */ + /* Bits 3-7: Reserved or unimplemented */ + +/* PHY Registers ************************************************************/ + +#define ENC_PHCON1 (0x00) /* PHY Control Register 1 */ +#define ENC_PHSTAT1 (0x01) /* PHY Status 1 */ +#define ENC_PHID1 (0x02) /* PHY ID Register 1 */ +#define ENC_PHID2 (0x03) /* PHY ID Register 2 */ +#define ENC_PHCON2 (0x10) /* PHY Control Register 2 */ +#define ENC_PHSTAT2 (0x11) /* PHY Status 2 */ +#define ENC_PHIE (0x12) /* PHY Interrupt Enable Register */ +#define ENC_PHIR (0x13) /* PHY Interrupt Request Register */ +#define ENC_PHLCON (0x14) + +/* PHY Control Register 1 Register Bit Definitions */ + +#define PHCON1_PDPXMD (1 << 8) /* Bit 8: PHY Duplex Mode */ +#define PHCON1_PPWRSV (1 << 11) /* Bit 11: PHY Power-Down */ +#define PHCON1_PLOOPBK (1 << 14) /* Bit 14: PHY Loopback */ +#define PHCON1_PRST (1 << 15) /* Bit 15: PHY Software Reset */ + +/* PHY Status 1 Register Bit Definitions */ + +#define PHSTAT1_JBSTAT (1 << 1) /* Bit 1: PHY Latching Jabber Status */ +#define PHSTAT1_LLSTAT (1 << 2) /* Bit 2: PHY Latching Link Status */ +#define PHSTAT1_PHDPX (1 << 11) /* Bit 11: PHY Half-Duplex Capable */ +#define PHSTAT1_PFDPX (1 << 12) /* Bit 12: PHY Full-Duplex Capable */ + +/* PHY Control Register 2 Register Bit Definitions */ + +#define PHCON2_HDLDIS (1 << 8) /* Bit 8: PHY Half-Duplex Loopback Disable */ +#define PHCON2_JABBER (1 << 10) /* Bit 10: Jabber Correction Disable */ +#define PHCON2_TXDIS (1 << 13) /* Bit 13: Twisted-Pair Transmitter Disable */ +#define PHCON2_FRCLINK (1 << 14) /* Bit 14: PHY Force Linkup */ + +/* PHY Status 2 Register Bit Definitions */ + +#define PHSTAT2_PLRITY (1 << 5) /* Bit 5: Polarity Status */ +#define PHSTAT2_DPXSTAT (1 << 9) /* Bit 9: PHY Duplex Status */ +#define PHSTAT2_LSTAT (1 << 10) /* Bit 10: PHY Link Status */ +#define PHSTAT2_COLSTAT (1 << 11) /* Bit 11: PHY Collision Status */ +#define PHSTAT2_RXSTAT (1 << 12) /* Bit 12: PHY Receive Status */ +#define PHSTAT2_TXSTAT (1 << 13) /* Bit 13: PHY Transmit Status */ + +/* PHY Interrupt Enable Register Bit Definitions */ + +#define PHIE_PGEIE (1 << 1) /* Bit 1: PHY Global Interrupt Enable */ +#define PHIE_PLNKIE (1 << 4) /* Bit 4: PHY Link Change Interrupt Enable */ + +/* PHIR Regiser Bit Definitions */ + +#define PHIR_PGIF (1 << 2) /* Bit 2: PHY Global Interrupt */ +#define PHIR_PLNKIF (1 << 4) /* Bit 4: PHY Link Change Interrupt */ + +/* PHLCON Regiser Bit Definitions */ + /* Bit 0: Reserved */ +#define PHLCON_STRCH (1 << 1) /* Bit 1: LED Pulse Stretching Enable */ +#define PHLCON_LFRQ0 (1 << 2) /* Bit 2: LED Pulse Stretch Time Configuration */ +#define PHLCON_LFRQ1 (1 << 3) /* Bit 3: " " " " " " " " " */ +#define PHLCON_LBCFG0 (1 << 4) /* Bit 4: LEDB Configuration */ +#define PHLCON_LBCFG1 (1 << 5) /* Bit 5: " " " " */ +#define PHLCON_LBCFG2 (1 << 6) /* Bit 6: " " " " */ +#define PHLCON_LBCFG3 (1 << 7) /* Bit 7: " " " " */ +#define PHLCON_LACFG0 (1 << 8) /* Bit 8: LEDA Configuration */ +#define PHLCON_LACFG1 (1 << 9) /* Bit 9: " " " " */ +#define PHLCON_LACFG2 (1 << 10) /* Bit 10: " " " " */ +#define PHLCON_LACFG3 (1 << 11) /* Bit 11: " " " " */ + +/* Packet Memory ************************************************************/ + +/* 8-Kbyte Transmit/Receive Packet Dual Port SRAM */ + +#define PKTMEM_START 0x0000 +#define PKTMEM_END 0x1fff + +/* maximum transfer unit */ +#define CONFIG_NET_ETH_MTU 1500 + +/* Packet Control Bits Definitions ******************************************/ + +#define PKTCTRL_POVERRIDE (1 << 0) /* Bit 0: Per Packet Override */ +#define PKTCTRL_PCRCEN (1 << 1) /* Bit 1: Per Packet CRC Enable */ +#define PKTCTRL_PPADEN (1 << 2) /* Bit 2: Per Packet Padding Enable */ +#define PKTCTRL_PHUGEEN (1 << 3) /* Bit 3: Per Packet Huge Frame Enable */ + +/* RX Status Bit Definitions ************************************************/ + +#define RXSTAT_LDEVENT (1 << 0) /* Bit 0: Long event or pack dropped */ + /* Bit 1: Reserved */ +#define RXSTAT_CEPS (1 << 2) /* Bit 2: Carrier event previously seen */ + /* Bit 3: Reserved */ +#define RXSTAT_CRCERROR (1 << 4) /* Bit 4: Frame CRC field bad */ +#define RXSTAT_LENERROR (1 << 5) /* Bit 5: Packet length != data length */ +#define RXSTAT_LENRANGE (1 << 6) /* Bit 6: Type/length field > 1500 bytes */ +#define RXSTAT_OK (1 << 7) /* Bit 7: Packet with valid CRC and no symbol errors */ +#define RXSTAT_MCAST (1 << 8) /* Bit 8: Packet with multicast address */ +#define RXSTAT_BCAST (1 << 9) /* Bit 9: Packet with broadcast address */ +#define RXSTAT_DRIBBLE (1 << 10) /* Bit 10: Additional bits received after packet */ +#define RXSTAT_CTRLFRAME (1 << 11) /* Bit 11: Control frame with valid type/length */ +#define RXSTAT_PAUSE (1 << 12) /* Bit 12: Control frame with pause frame opcde */ +#define RXSTAT_UNKOPCODE (1 << 13) /* Bit 13: Control frame with unknown opcode */ +#define RXSTAT_VLANTYPE (1 << 14) /* Bit 14: Current frame is a VLAN tagged frame */ + /* Bit 15: Zero */ +/* TSV bit definitions */ +#define TSV_LATECOL (1 << 5) /* Bit 5: Late Collision Error, RSV byte 3 */ + + + +/** @defgroup ETH_Duplex_Mode ETH Duplex Mode + * @{ + */ +#define ETH_MODE_FULLDUPLEX ((uint32_t)0x00000800) +#define ETH_MODE_HALFDUPLEX ((uint32_t)0x00000000) +/** + * @} + */ + +/** @defgroup ETH_Rx_Mode ETH Rx Mode + * @{ + */ +#define ETH_RXPOLLING_MODE ((uint32_t)0x00000000) +#define ETH_RXINTERRUPT_MODE ((uint32_t)0x00000001) +/** + * @} + */ + +/** @defgroup ETH_Checksum_Mode ETH Checksum Mode + * @{ + */ +#define ETH_CHECKSUM_BY_HARDWARE ((uint32_t)0x00000000) +#define ETH_CHECKSUM_BY_SOFTWARE ((uint32_t)0x00000001) +/** + * @} + */ + +/* Exported functions --------------------------------------------------------*/ +/** @addtogroup SPI_Exported_Functions + * @{ + */ + +/** @addtogroup SPI_Exported_Functions_Group1 + * @{ + */ +/* Initialization/de-initialization functions **********************************/ + + +/** + * Initialize the enc28j60 and configure the needed hardware resources + * param handle: Handle on data configuration. + * retval None + */ + +bool ENC_Start(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_SetMacAddr + * + * Description: + * Set the MAC address to the configured value. This is done after ifup + * or after a TX timeout. Note that this means that the interface must + * be down before configuring the MAC addr. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_SetMacAddr(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_RestoreTXBuffer + * + * Description: + * Prepare TX buffer + * + * Parameters: + * handle - Reference to the driver state structure + * len - length of buffer + * + * Returned Value: + * ERR_OK 0 No error, everything OK. + * ERR_MEM -1 Out of memory error. + * ERR_TIMEOUT -3 Timeout. + * + * Assumptions: + * + ****************************************************************************/ + +int8_t ENC_RestoreTXBuffer(ENC_HandleTypeDef *handle, uint16_t len); + +/**************************************************************************** + * Function: ENC_WriteBuffer + * + * Description: + * Write a buffer of data. + * + * Parameters: + * buffer - A pointer to the buffer to write from + * buflen - The number of bytes to write + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +void ENC_WriteBuffer(void *buffer, uint16_t buflen); + +/**************************************************************************** + * Function: ENC_Transmit + * + * Description: + * Start hardware transmission. Called either from: + * + * - pkif interrupt when an application responds to the receipt of data + * by trying to send something, or + * - From watchdog based polling. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * none + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef USE_PROTOTHREADS +PT_THREAD(ENC_Transmit(struct pt *pt, ENC_HandleTypeDef *handle)); +#else +void ENC_Transmit(ENC_HandleTypeDef *handle); +#endif + + /** + * @} + */ + + /** + * @} + */ + +/**************************************************************************** + * Function: ENC_GetReceivedFrame + * + * Description: + * Check if we have received packet, and if so, retrive them. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * true if new packet is available; false otherwise + * + * Assumptions: + * + ****************************************************************************/ + +bool ENC_GetReceivedFrame(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_IRQHandler + * + * Description: + * Perform interrupt handling logic outside of the interrupt handler (on + * the work queue thread). + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_IRQHandler(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_EnableInterrupts + * + * Description: + * Enable individual ENC28J60 interrupts + * + * Parameters: + * bits - The individual bits to enable + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_EnableInterrupts(uint8_t bits); + +/**************************************************************************** + * Function: ENC_GetPkcnt + * + * Description: + * Get the number of pending receive packets + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * the number of receive packet not processed yet + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_GetPkcnt(ENC_HandleTypeDef *handle); + + +/**************************************************************************** + * Function: up_udelay + * + * Description: + * wait us �s + * + * Parameters: + * us - The amount of time to wait in �s + * + * Returned Value: + * none + * + * Assumptions: + * + ****************************************************************************/ + +void up_udelay(uint32_t us); + +#endif /* ENC28J60_H_INCLUDED */ diff --git a/part14-spi-ethernet-interrupt/net/encspi.c b/part14-spi-ethernet-interrupt/net/encspi.c new file mode 100644 index 0000000..524f73f --- /dev/null +++ b/part14-spi-ethernet-interrupt/net/encspi.c @@ -0,0 +1,21 @@ +#include "../include/spi.h" + +void ENC_SPI_Select(unsigned char truefalse) { + spi_chip_select(!truefalse); // If it's true, select 0 (the ENC), if false, select 1 (i.e. deselect the ENC) +} + +void ENC_SPI_SendBuf(unsigned char *master2slave, unsigned char *slave2master, unsigned short bufferSize) { + spi_chip_select(0); + spi_send_recv(master2slave, slave2master, bufferSize); + spi_chip_select(1); // De-select the ENC +} + +void ENC_SPI_Send(unsigned char command) { + spi_chip_select(0); + spi_send(&command, 1); + spi_chip_select(1); // De-select the ENC +} + +void ENC_SPI_SendWithoutSelection(unsigned char command) { + spi_send(&command, 1); +} diff --git a/part14-spi-ethernet/lib/io.c b/part14-spi-ethernet/lib/io.c index 34a02aa..77e23eb 100644 --- a/part14-spi-ethernet/lib/io.c +++ b/part14-spi-ethernet/lib/io.c @@ -19,8 +19,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part15-tcpip-webserver/Makefile b/part15-tcpip-webserver/Makefile index 7c9e887..69d33d3 100644 --- a/part15-tcpip-webserver/Makefile +++ b/part15-tcpip-webserver/Makefile @@ -1,20 +1,25 @@ +BOOTMNT ?= /media/cvdeveloper/bootfs + CFILES = $(wildcard *.c lib/*.c kernel/*.c net/*.c tcpip/*.c) SFILES = $(wildcard boot/*.S lib/*.S kernel/*.S) OFILES = $(CFILES:.c=.o) $(SFILES:.S=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O0 -ffreestanding -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O0 -ffreestanding -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin all: clean kernel8.img %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ %.o: %.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib $(OFILES) -T boot/link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib $(OFILES) -T boot/link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img + cp kernel8.img $(BOOTMNT)/kernel8.img + cp config.txt $(BOOTMNT)/ + sync clean: /bin/rm kernel8.elf */*.o *.img > /dev/null 2> /dev/null || true diff --git a/part15-tcpip-webserver/config.txt b/part15-tcpip-webserver/config.txt new file mode 100644 index 0000000..41944de --- /dev/null +++ b/part15-tcpip-webserver/config.txt @@ -0,0 +1,53 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] +kernel_old=1 +disable_commandline_tags=1 +core_freq_min=500 +hdmi_group=1 +hdmi_mode=16 diff --git a/part15-tcpip-webserver/kernel/kernel.c b/part15-tcpip-webserver/kernel/kernel.c index 420afa1..439c283 100644 --- a/part15-tcpip-webserver/kernel/kernel.c +++ b/part15-tcpip-webserver/kernel/kernel.c @@ -59,7 +59,7 @@ ENC_HandleTypeDef handle; unsigned char myMAC[6] = { 0xc0, 0xff, 0xee, 0xc0, 0xff, 0xee }; // IP address to be assigned to the ENC28J60 -unsigned char deviceIP[4] = { 192, 168, 0, 66 }; +unsigned char deviceIP[4] = { 10, 7, 3, 20 }; void init_network(void) { diff --git a/part15a-tcpip-webserver-gpio-interrupt/Makefile b/part15a-tcpip-webserver-gpio-interrupt/Makefile new file mode 100644 index 0000000..389f0bd --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/Makefile @@ -0,0 +1,26 @@ + +BOOTMNT ?= /media/cvdeveloper/bootfs + +CFILES = $(wildcard *.c lib/*.c kernel/*.c net/*.c tcpip/*.c) +SFILES = $(wildcard boot/*.S lib/*.S kernel/*.S) +OFILES = $(CFILES:.c=.o) $(SFILES:.S=.o) +GCCFLAGS = -Wall -O2 -ffreestanding -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin + +all: clean kernel8.img + +%.o: %.c + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ + +%.o: %.S + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ + +kernel8.img: $(OFILES) + $(GCCPATH)/aarch64-none-elf-ld -nostdlib $(OFILES) -T boot/link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img + cp kernel8.img $(BOOTMNT)/kernel8.img + cp config.txt $(BOOTMNT)/ + sync + +clean: + /bin/rm kernel8.elf */*.o *.img > /dev/null 2> /dev/null || true diff --git a/part15a-tcpip-webserver-gpio-interrupt/README.md b/part15a-tcpip-webserver-gpio-interrupt/README.md new file mode 100644 index 0000000..2f8205a --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/README.md @@ -0,0 +1,96 @@ +Writing a "bare metal" operating system for Raspberry Pi 4 (Part 15) +==================================================================== + +[< Go back to part14-spi-ethernet](../part14-spi-ethernet) + +Adding a TCP/IP stack +--------------------- +Having achieved "proof of life" from our Ethernet module in _part14-spi-ethernet_, you're doubtless wondering how to go from there to serving web pages, posting tweets on Twitter or perhaps even just simply responding to a ping! + +This is where you'll need a fully-fledged TCP/IP stack that goes way beyond handcrafted ARPs, implementing many more protocols to achieve efficient bi-directional communication. + +In this part we make use of some code from Guido Socher of [tuxgraphics.org](http://tuxgraphics.org/), designed to be a lightweight TCP/IP stack for embedded devices. I chose this because it was super simple to get working (or "port"), but you might want to look at [LwIP](https://en.wikipedia.org/wiki/LwIP) if you need something more advanced. + +The code +-------- +Most of the new code is in the _tcpip/_ subdirectory. I actually came across it in [this tarball](http://tuxgraphics.org/common/src2/article09051/eth_tcp_client_server-dhcp-5.10.tar.gz) and, again, made only a very few cosmetic changes (`diff` is your friend!). + +It did require me to expose the `strlen()` function we implemented in _lib/fb.c_, so that's added to _include/fb.h_. Similarly, we expose the `memcpy()` function we implemented in _kernel/kernel.c_, so that's added to _kernel/kernel.h_. + +I also needed a single function that tells the ENC to send a packet. Nothing new here, just different packaging: + +```c +void enc28j60PacketSend(unsigned short buflen, void *buffer) { + if (ENC_RestoreTXBuffer(&handle, buflen) == 0) { + ENC_WriteBuffer((unsigned char *) buffer, buflen); + handle.transmitLength = buflen; + ENC_Transmit(&handle); + } +} +``` + +This was also added to _kernel/kernel.h_. + +What happened to _arp.c_? +------------------------- +You'll notice that I've merged _arp.c_ and _kernel.c_. I have also gone away from doing anything wih multicore or IRQ timers to keep this kernel simple. We still initialise the network card in exactly the same way but, when we're done, we call this function in Guido's code: + +```c +init_udp_or_www_server(myMAC, deviceIP); +``` + +This tells the TCP/IP library who we are, so we're all on the same page! + +Finally, and aside from a little cleanup (eg. moving the HAL/system timer functions to _lib/io.c_ with the commensurate changes to _include/io.h_), the major change is the new `serve()` function: + +```c +void serve(void) +{ + while (1) { + while (!ENC_GetReceivedFrame(&handle)); + + uint8_t *buf = (uint8_t *)handle.RxFrameInfos.buffer; + uint16_t len = handle.RxFrameInfos.length; + uint16_t dat_p = packetloop_arp_icmp_tcp(buf, len); + + if (dat_p != 0) { + debugstr("Incoming web request... "); + + if (strncmp("GET ", (char *)&(buf[dat_p]), 4) != 0) { + debugstr("not GET"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n

ERROR

"); + } else { + if (strncmp("/ ", (char *)&(buf[dat_p+4]), 2) == 0) { + // just one web page in the "root directory" of the web server + debugstr("GET root"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n

Hello world!

"); + } else { + // just one web page not in the "root directory" of the web server + debugstr("GET not root"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n

Goodbye cruel world.

"); + } + } + + www_server_reply(buf, dat_p); // send web page data + debugcrlf(); + } + } +} +``` + +This is an infinite loop which waits for an incoming packet and then firstly passes it to Guido's `packetloop_arp_icmp_tcp()` function. This function implements some useful things, like responding to pings. I modified the routine to print a message to the screen when it sends a "pong" (look from line 1371 of _tcpip/ip_arp_udp_tcp.c_), so we can see when it's in action! + +Examining the return value of `packetloop_arp_icmp_tcp()` then allows us to check whether there is an incoming web request, since we've configured the TCP/IP library to be a web server in _tcpip/ip_config.h_ with `#define WWW_server`. + +We then serve responses based on three possible cases: + + * The incoming request is not a GET request (eg. maybe it's a HEAD request) - you can simulate this using the `curl` tool: `curl -I 192.168.0.66` + * The incoming request is a GET request for the root web page `/` - `curl 192.168.0.66/` + * The incoming request is a GET request for any non-root web page - eg. `curl 192.168.0.66/babbleberry/monkey` + +I recommend reading [this page](http://tuxgraphics.org/electronics/200905/embedded-tcp-ip-stack.shtml) for a full explanation. The code I have ported is very similar to what you see there. + +_Imagine my excitement when I built, ran and could ping my RPi4 at 192.168.0.66 and get a web response to my browser on both my laptop and my iPhone!_ + +![Pinging from my iPhone](images/15-tcpip-webserver-pinging.jpg) +![Browsing from my laptop](images/15-tcpip-webserver-browser.png) diff --git a/part15a-tcpip-webserver-gpio-interrupt/boot/boot.S b/part15a-tcpip-webserver-gpio-interrupt/boot/boot.S new file mode 100644 index 0000000..f644aee --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/boot/boot.S @@ -0,0 +1,114 @@ +#include "sysregs.h" + +#define LOCAL_CONTROL 0xff800000 +#define LOCAL_PRESCALER 0xff800008 +#define OSC_FREQ 54000000 +#define MAIN_STACK 0x400000 + +.section ".text.boot" // Make sure the linker puts this at the start of the kernel image + +.global _start // Execution starts here + +_start: + ldr x0, =LOCAL_CONTROL // Sort out the timer + str wzr, [x0] + mov w1, 0x80000000 + str w1, [x0, #(LOCAL_PRESCALER - LOCAL_CONTROL)] + + ldr x0, =OSC_FREQ + msr cntfrq_el0, x0 + msr cntvoff_el2, xzr + + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + + // We're not on the main core, so hang in an infinite wait loop + adr x5, spin_cpu0 +1: wfe + ldr x4, [x5, x1, lsl #3] + cbz x4, 1b + + ldr x2, =__stack_start // Get ourselves a fresh stack - location depends on CPU core asking + lsl x1, x1, #9 // Multiply core_number by 512 + add x3, x2, x1 // Add to the address + mov sp, x3 + + mov x0, #0 + mov x1, #0 + mov x2, #0 + mov x3, #0 + br x4 + b 1b +2: // We're on the main core! + // First enable the FPU + + mov x0, #0x33ff + msr cptr_el3, x0 // Disable coprocessor traps to EL3 + mov x0, #3 << 20 + msr cpacr_el1, x0 // Enable FP/SIMD at EL1 + + // Now get ready to switch from EL3 down to EL1 + + ldr x0, =SCTLR_VALUE_MMU_DISABLED + msr sctlr_el1, x0 + + ldr x0, =HCR_VALUE + msr hcr_el2, x0 + + ldr x0, =SCR_VALUE + msr scr_el3, x0 + + ldr x0, =SPSR_VALUE + msr spsr_el3, x0 + + adr x0, el1_entry + msr elr_el3, x0 + + eret +el1_entry: + // We're in EL1 + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Set stack to start somewhere safe + mov sp, #MAIN_STACK + + // Jump to our main() routine in C (make sure it doesn't return) +4: bl main + // In case it does return, halt the master core too + b 1b + +.ltorg + +.org 0x110 +.globl spin_cpu0 +spin_cpu0: + .quad 0 + +.org 0x118 +.globl spin_cpu1 +spin_cpu1: + .quad 0 + +.org 0x120 +.globl spin_cpu2 +spin_cpu2: + .quad 0 + +.org 0x128 +.globl spin_cpu3 +spin_cpu3: + .quad 0 + +.globl get_el +get_el: + mrs x0, CurrentEL + lsr x0, x0, #2 + ret diff --git a/part15a-tcpip-webserver-gpio-interrupt/boot/link.ld b/part15a-tcpip-webserver-gpio-interrupt/boot/link.ld new file mode 100644 index 0000000..4e84390 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/boot/link.ld @@ -0,0 +1,35 @@ +SECTIONS +{ + .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) } + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) } + PROVIDE(_data = .); + .data : { *(.data .data.* .gnu.linkonce.d*) } + .bss (NOLOAD) : { + . = ALIGN(16); + __bss_start = .; + *(.bss .bss.*) + *(COMMON) + __bss_end = .; + __bss_size = (__bss_end - __bss_start)>>3; + } + .cpu1Stack : + { + . = ALIGN(16); + __stack_start = .; + . = . + 512; + __cpu1_stack = .; + } + .cpu2Stack : + { + . = . + 512; + __cpu2_stack = .; + } + .cpu3Stack : + { + . = . + 512; + __cpu3_stack = .; + } + _end = .; + + /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) } +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/boot/sysregs.h b/part15a-tcpip-webserver-gpio-interrupt/boot/sysregs.h new file mode 100644 index 0000000..385cd79 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/boot/sysregs.h @@ -0,0 +1,44 @@ +#ifndef _SYSREGS_H +#define _SYSREGS_H + +// *************************************** +// SCTLR_EL1, System Control Register (EL1), Page 2654 of AArch64-Reference-Manual. +// *************************************** + +#define SCTLR_RESERVED (3 << 28) | (3 << 22) | (1 << 20) | (1 << 11) +#define SCTLR_EE_LITTLE_ENDIAN (0 << 25) +#define SCTLR_EOE_LITTLE_ENDIAN (0 << 24) +#define SCTLR_I_CACHE_DISABLED (0 << 12) +#define SCTLR_D_CACHE_DISABLED (0 << 2) +#define SCTLR_I_CACHE_ENABLED (1 << 12) +#define SCTLR_D_CACHE_ENABLED (1 << 2) +#define SCTLR_MMU_DISABLED (0 << 0) +#define SCTLR_MMU_ENABLED (1 << 0) + +#define SCTLR_VALUE_MMU_DISABLED (SCTLR_RESERVED | SCTLR_EE_LITTLE_ENDIAN | SCTLR_I_CACHE_ENABLED | SCTLR_D_CACHE_ENABLED | SCTLR_MMU_DISABLED) + +// *************************************** +// HCR_EL2, Hypervisor Configuration Register (EL2), Page 2487 of AArch64-Reference-Manual. +// *************************************** + +#define HCR_RW (1 << 31) +#define HCR_VALUE HCR_RW + +// *************************************** +// SCR_EL3, Secure Configuration Register (EL3), Page 2648 of AArch64-Reference-Manual. +// *************************************** + +#define SCR_RESERVED (3 << 4) +#define SCR_RW (1 << 10) +#define SCR_NS (1 << 0) +#define SCR_VALUE (SCR_RESERVED | SCR_RW | SCR_NS) + +// *************************************** +// SPSR_EL3, Saved Program Status Register (EL3) Page 389 of AArch64-Reference-Manual. +// *************************************** + +#define SPSR_MASK_ALL (7 << 6) +#define SPSR_EL1h (5 << 0) +#define SPSR_VALUE (SPSR_MASK_ALL | SPSR_EL1h) + +#endif diff --git a/part15a-tcpip-webserver-gpio-interrupt/config.txt b/part15a-tcpip-webserver-gpio-interrupt/config.txt new file mode 100644 index 0000000..41944de --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/config.txt @@ -0,0 +1,53 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[cm4] +# Enable host mode on the 2711 built-in XHCI USB controller. +# This line should be removed if the legacy DWC2 controller is required +# (e.g. for USB device mode) or if USB support is not required. +otg_mode=1 + +[all] +kernel_old=1 +disable_commandline_tags=1 +core_freq_min=500 +hdmi_group=1 +hdmi_mode=16 diff --git a/part15a-tcpip-webserver-gpio-interrupt/images/15-tcpip-webserver-browser.png b/part15a-tcpip-webserver-gpio-interrupt/images/15-tcpip-webserver-browser.png new file mode 100644 index 0000000..14cfdc0 Binary files /dev/null and b/part15a-tcpip-webserver-gpio-interrupt/images/15-tcpip-webserver-browser.png differ diff --git a/part15a-tcpip-webserver-gpio-interrupt/images/15-tcpip-webserver-pinging.jpg b/part15a-tcpip-webserver-gpio-interrupt/images/15-tcpip-webserver-pinging.jpg new file mode 100755 index 0000000..a7cb280 Binary files /dev/null and b/part15a-tcpip-webserver-gpio-interrupt/images/15-tcpip-webserver-pinging.jpg differ diff --git a/part15a-tcpip-webserver-gpio-interrupt/include/fb.h b/part15a-tcpip-webserver-gpio-interrupt/include/fb.h new file mode 100644 index 0000000..88e0ee4 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/include/fb.h @@ -0,0 +1,14 @@ +void fb_init(); +void drawPixel(int x, int y, unsigned char attr); +void drawChar(unsigned char ch, int x, int y, unsigned char attr, int zoom); +void drawString(int x, int y, char *s, unsigned char attr, int zoom); +void drawRect(int x1, int y1, int x2, int y2, unsigned char attr, int fill); +void drawCircle(int x0, int y0, int radius, unsigned char attr, int fill); +void drawLine(int x1, int y1, int x2, int y2, unsigned char attr); +void moveRect(int oldx, int oldy, int width, int height, int shiftx, int shifty, unsigned char attr); +void wait_msec(unsigned int n); +void debugstr(char *str); +void debugcrlf(void); +void debugch(unsigned char b); +void debughex(unsigned int d); +int strlen(const char *str); diff --git a/part15a-tcpip-webserver-gpio-interrupt/include/io.h b/part15a-tcpip-webserver-gpio-interrupt/include/io.h new file mode 100644 index 0000000..707ae63 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/include/io.h @@ -0,0 +1,21 @@ +#define PERIPHERAL_BASE 0xFE000000 +#define LEGACY_BASE 0x7E000000 +#define SAFE_ADDRESS 0x00400000 // Somewhere safe to store a lot of data + +void uart_init(); +void uart_writeText(char *buffer); +void uart_loadOutputFifo(); +unsigned char uart_readByte(); +unsigned int uart_isReadByteReady(); +void uart_writeByteBlockingActual(unsigned char ch); +void uart_update(); +void mmio_write(long reg, unsigned int val); +unsigned int mmio_read(long reg); +void gpio_useAsAlt0(unsigned int pin_number); +void gpio_useAsAlt3(unsigned int pin_number); +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff); +void gpio_initOutputPinWithPullNone(unsigned int pin_number); +void uart_hex(unsigned int d); +void uart_byte(unsigned char b); +unsigned long HAL_GetTick(void); +void HAL_Delay(unsigned int ms); diff --git a/part15a-tcpip-webserver-gpio-interrupt/include/mb.h b/part15a-tcpip-webserver-gpio-interrupt/include/mb.h new file mode 100644 index 0000000..0e1861b --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/include/mb.h @@ -0,0 +1,34 @@ +extern volatile unsigned int mbox[36]; + +enum { + MBOX_REQUEST = 0 +}; + +enum { + MBOX_CH_POWER = 0, + MBOX_CH_FB = 1, + MBOX_CH_VUART = 2, + MBOX_CH_VCHIQ = 3, + MBOX_CH_LEDS = 4, + MBOX_CH_BTNS = 5, + MBOX_CH_TOUCH = 6, + MBOX_CH_COUNT = 7, + MBOX_CH_PROP = 8 // Request from ARM for response by VideoCore +}; + +enum { + MBOX_TAG_SETPOWER = 0x28001, + MBOX_TAG_SETCLKRATE = 0x38002, + + MBOX_TAG_SETPHYWH = 0x48003, + MBOX_TAG_SETVIRTWH = 0x48004, + MBOX_TAG_SETVIRTOFF = 0x48009, + MBOX_TAG_SETDEPTH = 0x48005, + MBOX_TAG_SETPXLORDR = 0x48006, + MBOX_TAG_GETFB = 0x40001, + MBOX_TAG_GETPITCH = 0x40008, + + MBOX_TAG_LAST = 0 +}; + +unsigned int mbox_call(unsigned char ch); diff --git a/part15a-tcpip-webserver-gpio-interrupt/include/multicore.h b/part15a-tcpip-webserver-gpio-interrupt/include/multicore.h new file mode 100644 index 0000000..4c3dc0f --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/include/multicore.h @@ -0,0 +1,11 @@ +extern unsigned int spin_cpu0; +extern unsigned int spin_cpu1; +extern unsigned int spin_cpu2; +extern unsigned int spin_cpu3; + +void start_core1(void (*func)(void)); +void start_core2(void (*func)(void)); +void start_core3(void (*func)(void)); +void clear_core1(void); +void clear_core2(void); +void clear_core3(void); diff --git a/part15a-tcpip-webserver-gpio-interrupt/include/spi.h b/part15a-tcpip-webserver-gpio-interrupt/include/spi.h new file mode 100644 index 0000000..bf57e3e --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/include/spi.h @@ -0,0 +1,5 @@ +void spi_init(); +void spi_send_recv(unsigned char *sbuffer, unsigned char *rbuffer, unsigned int size); +void spi_send(unsigned char *data, unsigned int size); +void spi_recv(unsigned char *data, unsigned int size); +void spi_chip_select(unsigned char chip_select); diff --git a/part15a-tcpip-webserver-gpio-interrupt/include/terminal.h b/part15a-tcpip-webserver-gpio-interrupt/include/terminal.h new file mode 100644 index 0000000..ef05b85 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/include/terminal.h @@ -0,0 +1,253 @@ +unsigned int vgapal[] = { + 0x000000, + 0x0000AA, + 0x00AA00, + 0x00AAAA, + 0xAA0000, + 0xAA00AA, + 0xAA5500, + 0xAAAAAA, + 0x555555, + 0x5555FF, + 0x55FF55, + 0x55FFFF, + 0xFF5555, + 0xFF55FF, + 0xFFFF55, + 0xFFFFFF +}; + +enum { + FONT_WIDTH = 8, + FONT_HEIGHT = 8, + FONT_BPG = 8, // Bytes per glyph + FONT_BPL = 1, // Bytes per line + FONT_NUMGLYPHS = 224 +}; + +unsigned char font[FONT_NUMGLYPHS][FONT_BPG] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) + { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) + { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") + { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) + { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) + { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) + { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) + { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') + { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() + { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) + { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) + { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) + { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) + { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) + { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) + { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) + { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) + { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) + { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) + { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) + { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) + { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) + { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) + { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) + { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) + { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) + { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) + { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) + { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) + { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) + { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) + { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) + { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) + { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) + { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) + { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) + { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) + { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) + { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) + { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) + { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) + { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) + { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) + { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) + { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) + { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) + { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) + { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) + { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) + { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) + { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) + { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) + { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) + { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) + { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) + { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) + { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) + { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) + { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) + { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) + { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) + { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) + { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) + { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) + { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) + { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) + { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) + { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) + { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) + { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) + { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) + { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) + { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) + { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) + { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) + { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) + { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) + { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) + { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) + { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) + { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F + { 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x18, 0x30, 0x1E}, // U+00C7 (C cedille) + { 0x00, 0x33, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FC (u umlaut) + { 0x38, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00E9 (e aigu) + { 0x7E, 0xC3, 0x3C, 0x60, 0x7C, 0x66, 0xFC, 0x00}, // U+00E2 (a circumflex) + { 0x33, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E4 (a umlaut) + { 0x07, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E0 (a grave) + { 0x0C, 0x0C, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E5 (a ring) + { 0x00, 0x00, 0x1E, 0x03, 0x03, 0x1E, 0x30, 0x1C}, // U+00E7 (c cedille) + { 0x7E, 0xC3, 0x3C, 0x66, 0x7E, 0x06, 0x3C, 0x00}, // U+00EA (e circumflex) + { 0x33, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00EB (e umlaut) + { 0x07, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00E8 (e grave) + { 0x33, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00EF (i umlaut) + { 0x3E, 0x63, 0x1C, 0x18, 0x18, 0x18, 0x3C, 0x00}, // U+00EE (i circumflex) + { 0x07, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00EC (i grave) + { 0x63, 0x1C, 0x36, 0x63, 0x7F, 0x63, 0x63, 0x00}, // U+00C4 (A umlaut) + { 0x0C, 0x0C, 0x00, 0x1E, 0x33, 0x3F, 0x33, 0x00}, // U+00C5 (A ring) + { 0x07, 0x00, 0x3F, 0x06, 0x1E, 0x06, 0x3F, 0x00}, // U+00C8 (E grave) + { 0x00, 0x00, 0xFE, 0x30, 0xFE, 0x33, 0xFE, 0x00}, // U+00E6 (ae) + { 0x7C, 0x36, 0x33, 0x7F, 0x33, 0x33, 0x73, 0x00}, // U+00C6 (AE) + { 0x1E, 0x33, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F4 (o circumflex) + { 0x00, 0x33, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F6 (o umlaut) + { 0x00, 0x07, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F2 (o grave) + { 0x1E, 0x33, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FB (u circumflex) + { 0x00, 0x07, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00F9 (u grave) + { 0x00, 0x33, 0x00, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+00FF (y umlaut) + { 0xC3, 0x18, 0x3C, 0x66, 0x66, 0x3C, 0x18, 0x00}, // U+00D6 (O umlaut) + { 0x33, 0x00, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+00DC (U umlaut) + { 0x18, 0x18, 0x7E, 0x03, 0x03, 0x7E, 0x18, 0x18}, // U+00A2 (dollarcents) + { 0x1C, 0x36, 0x26, 0x0F, 0x06, 0x67, 0x3F, 0x00}, // U+00A3 (pound sterling) + { 0x33, 0x33, 0x1E, 0x3F, 0x0C, 0x3F, 0x0C, 0x0C}, // U+00A5 (yen) + { 0x7C, 0xC6, 0x1C, 0x36, 0x36, 0x1C, 0x33, 0x1E}, // U+00A7 (paragraph) + { 0x70, 0xD8, 0x18, 0x3C, 0x18, 0x18, 0x1B, 0x0E}, // U+0192 (dutch florijn) + { 0x38, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E1 (a aigu) + { 0x1C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00ED (i augu) + { 0x00, 0x38, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F3 (o aigu) + { 0x00, 0x38, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FA (u aigu) + { 0x00, 0x1F, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x00}, // U+00F1 (n ~) + { 0x3F, 0x00, 0x33, 0x37, 0x3F, 0x3B, 0x33, 0x00}, // U+00D1 (N ~) + { 0x3C, 0x36, 0x36, 0x7C, 0x00, 0x00, 0x00, 0x00}, // U+00AA (superscript a) + { 0x1C, 0x36, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00}, // U+00BA (superscript 0) + { 0x0C, 0x00, 0x0C, 0x06, 0x03, 0x33, 0x1E, 0x00}, // U+00BF (inverted ?) + { 0x00, 0x00, 0x00, 0x3F, 0x03, 0x03, 0x00, 0x00}, // U+2310 (gun pointing right) + { 0x00, 0x00, 0x00, 0x3F, 0x30, 0x30, 0x00, 0x00}, // U+00AC (gun pointing left) + { 0xC3, 0x63, 0x33, 0x7B, 0xCC, 0x66, 0x33, 0xF0}, // U+00BD (1/2) + { 0xC3, 0x63, 0x33, 0xBD, 0xEC, 0xF6, 0xF3, 0x03}, // U+00BC (1/4) + { 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00}, // U+00A1 (inverted !) + { 0x00, 0xCC, 0x66, 0x33, 0x66, 0xCC, 0x00, 0x00}, // U+00AB (<<) + { 0x00, 0x33, 0x66, 0xCC, 0x66, 0x33, 0x00, 0x00}, // U+00BB (>>) + { 0x55, 0x00, 0xAA, 0x00, 0x55, 0x00, 0xAA, 0x00}, // U+2591 (25% solid) + { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA}, // U+2592 (50% solid) + { 0xFF, 0xAA, 0xFF, 0x55, 0xFF, 0xAA, 0xFF, 0x55}, // U+2593 (75% solid) + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}, // U+2502 (thin vertical) + { 0x08, 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, 0x08}, // U+2524 (down L, left L, up L) + { 0x08, 0x08, 0x08, 0x0F, 0x08, 0x0F, 0x08, 0x08}, // U+2561 (up L, down L, left D) + { 0x14, 0x14, 0x14, 0x14, 0x17, 0x14, 0x14, 0x14}, // U+2562 (up D, down D, left L) + { 0x00, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x14, 0x14}, // U+2556 (down D, left L) + { 0x00, 0x00, 0x00, 0x0F, 0x08, 0x0F, 0x08, 0x08}, // U+2555 (down L, left D) + { 0x14, 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14}, // U+2563 (up D, down D, left D) + { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14}, // U+2551 (double vertical) + { 0x00, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x14, 0x14}, // U+2557 (down D, left D) + { 0x14, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x00, 0x00}, // U+255D (up D, left D) + { 0x14, 0x14, 0x14, 0x14, 0x1F, 0x00, 0x00, 0x00}, // U+255C (up D, left L) + { 0x08, 0x08, 0x08, 0x0F, 0x08, 0x0F, 0x00, 0x00}, // U+255B (up L, left D) + { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, 0x08}, // U+2510 (down L, left L) + { 0x08, 0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, 0x00}, // U+2514 (up L, right L) + { 0x08, 0x08, 0x08, 0x08, 0xff, 0x00, 0x00, 0x00}, // U+2534 (up L, right L, left L) + { 0x00, 0x00, 0x00, 0x00, 0xff, 0x08, 0x08, 0x08}, // U+252C (down L, right L, left L) + { 0x08, 0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, 0x08}, // U+251C (down L, right L, up L) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00}, // U+2500 (thin horizontal) + { 0x08, 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, 0x08}, // U+253C (up L, right L, left L, down L) + { 0x08, 0x08, 0x08, 0xF8, 0x08, 0xF8, 0x08, 0x08}, // U+255E (up L, down L, right D) + { 0x14, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x14, 0x14}, // U+255F (up D, down D, right L) + { 0x14, 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x00, 0x00}, // U+255A (up D, right D) + { 0x00, 0x00, 0x00, 0xFC, 0x04, 0xF4, 0x14, 0x14}, // U+2554 (down D, right D) + { 0x14, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00}, // U+2569 (left D, right D, up D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14}, // U+2566 (left D, right D, down D) + { 0x14, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x14, 0x14}, // U+2560 (up D, down D, right D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00}, // U+2550 (double horizontal) + { 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14}, // U+256C (left D, right D, down D, up D) + { 0x08, 0x08, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0x00}, // U+2567 (left D, right D, up L) + { 0x14, 0x14, 0x14, 0x14, 0xFF, 0x00, 0x00, 0x00}, // U+2568 (left L, right L, up D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x08}, // U+2564 (left D, right D, down L) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x14, 0x14, 0x14}, // U+2565 (left L, right L, down D) + { 0x14, 0x14, 0x14, 0x14, 0xFC, 0x00, 0x00, 0x00}, // U+2559 (up D, right L) + { 0x08, 0x08, 0x08, 0xF8, 0x08, 0xF8, 0x00, 0x00}, // U+2558 (up L, right D) + { 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF8, 0x08, 0x08}, // U+2552 (down L, right D) + { 0x00, 0x00, 0x00, 0x00, 0xFC, 0x14, 0x14, 0x14}, // U+2553 (down D, right L) + { 0x14, 0x14, 0x14, 0x14, 0xFF, 0x14, 0x14, 0x14}, // U+256B (left L, right L, down D, up D) + { 0x08, 0x08, 0x08, 0xFF, 0x08, 0xFF, 0x08, 0x08}, // U+256A (left D, right D, down L, up L) + { 0x08, 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, 0x00}, // U+2518 (up L, left L) + { 0x00, 0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08}, // U+250C (down L, right L) + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // U+2588 (solid) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, // U+2584 (bottom half) + { 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}, // U+258C (left half) + { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0}, // U+2590 (right half) + { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, // U+2580 (top half) +}; diff --git a/part15a-tcpip-webserver-gpio-interrupt/kernel/irq.c b/part15a-tcpip-webserver-gpio-interrupt/kernel/irq.c new file mode 100644 index 0000000..6e83ca3 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/kernel/irq.c @@ -0,0 +1,27 @@ +#include "kernel.h" + +void enable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3; +} + +void disable_interrupt_controller() { + REGS_IRQ->irq0_enable_0 = 0; +} + +void handle_irq() { + unsigned int irq = REGS_IRQ->irq0_pending_0; + + while(irq & (SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3)) { + if (irq & SYS_TIMER_IRQ_1) { + irq &= ~SYS_TIMER_IRQ_1; + + handle_timer_1(); + } + + if (irq & SYS_TIMER_IRQ_3) { + irq &= ~SYS_TIMER_IRQ_3; + + handle_timer_3(); + } + } +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/kernel/irqentry.S b/part15a-tcpip-webserver-gpio-interrupt/kernel/irqentry.S new file mode 100644 index 0000000..d24ae00 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/kernel/irqentry.S @@ -0,0 +1,160 @@ +#define SYNC_INVALID_EL1t 0 +#define IRQ_INVALID_EL1t 1 +#define FIQ_INVALID_EL1t 2 +#define ERROR_INVALID_EL1t 3 + +#define SYNC_INVALID_EL1h 4 +#define IRQ_INVALID_EL1h 5 +#define FIQ_INVALID_EL1h 6 +#define ERROR_INVALID_EL1h 7 + +#define SYNC_INVALID_EL0_64 8 +#define IRQ_INVALID_EL0_64 9 +#define FIQ_INVALID_EL0_64 10 +#define ERROR_INVALID_EL0_64 11 + +#define SYNC_INVALID_EL0_32 12 +#define IRQ_INVALID_EL0_32 13 +#define FIQ_INVALID_EL0_32 14 +#define ERROR_INVALID_EL0_32 15 + +//stack frame size +#define S_FRAME_SIZE 256 + +.macro kernel_entry + sub sp, sp, #S_FRAME_SIZE + stp x0, x1, [sp, #16 * 0] + stp x2, x3, [sp, #16 * 1] + stp x4, x5, [sp, #16 * 2] + stp x6, x7, [sp, #16 * 3] + stp x8, x9, [sp, #16 * 4] + stp x10, x11, [sp, #16 * 5] + stp x12, x13, [sp, #16 * 6] + stp x14, x15, [sp, #16 * 7] + stp x16, x17, [sp, #16 * 8] + stp x18, x19, [sp, #16 * 9] + stp x20, x21, [sp, #16 * 10] + stp x22, x23, [sp, #16 * 11] + stp x24, x25, [sp, #16 * 12] + stp x26, x27, [sp, #16 * 13] + stp x28, x29, [sp, #16 * 14] + str x30, [sp, #16 * 15] +.endm + +.macro kernel_exit + ldp x0, x1, [sp, #16 * 0] + ldp x2, x3, [sp, #16 * 1] + ldp x4, x5, [sp, #16 * 2] + ldp x6, x7, [sp, #16 * 3] + ldp x8, x9, [sp, #16 * 4] + ldp x10, x11, [sp, #16 * 5] + ldp x12, x13, [sp, #16 * 6] + ldp x14, x15, [sp, #16 * 7] + ldp x16, x17, [sp, #16 * 8] + ldp x18, x19, [sp, #16 * 9] + ldp x20, x21, [sp, #16 * 10] + ldp x22, x23, [sp, #16 * 11] + ldp x24, x25, [sp, #16 * 12] + ldp x26, x27, [sp, #16 * 13] + ldp x28, x29, [sp, #16 * 14] + ldr x30, [sp, #16 * 15] + add sp, sp, #S_FRAME_SIZE + eret +.endm + +.macro handle_invalid_entry type + kernel_entry + mov x0, #\type + mrs x1, esr_el1 + mrs x2, elr_el1 + + // We could pass this to a function to print an error here + // e.g. bl show_invalid_entry_message + // + // For now we'll just hang + + b err_hang +.endm + +.macro ventry label +.align 7 + b \label +.endm + +//Exception vectors table +.align 11 +.globl vectors +vectors: + ventry sync_invalid_el1t // Synchronous EL1t + ventry irq_invalid_el1t // IRQ EL1t + ventry fiq_invalid_el1t // FIQ EL1t + ventry error_invalid_el1t // Error EL1t + + ventry sync_invalid_el1h // Synchronous EL1h + ventry handle_el1_irq // IRQ EL1h + ventry fiq_invalid_el1h // FIQ EL1h + ventry error_invalid_el1h // Error EL1h + + ventry sync_invalid_el0_64 // Synchronous 64-bit EL0 + ventry irq_invalid_el0_64 // IRQ 64-bit EL0 + ventry fiq_invalid_el0_64 // FIQ 64-bit EL0 + ventry error_invalid_el0_64 // Error 64-bit EL0 + + ventry sync_invalid_el0_32 // Synchronous 32-bit EL0 + ventry irq_invalid_el0_32 // IRQ 32-bit EL0 + ventry fiq_invalid_el0_32 // FIQ 32-bit EL0 + ventry error_invalid_el0_32 // Error 32-bit EL0 + + +sync_invalid_el1t: + handle_invalid_entry SYNC_INVALID_EL1t + +irq_invalid_el1t: + handle_invalid_entry IRQ_INVALID_EL1t + +fiq_invalid_el1t: + handle_invalid_entry FIQ_INVALID_EL1t + +error_invalid_el1t: + handle_invalid_entry ERROR_INVALID_EL1t + +sync_invalid_el1h: + handle_invalid_entry SYNC_INVALID_EL1h + +fiq_invalid_el1h: + handle_invalid_entry FIQ_INVALID_EL1h + +error_invalid_el1h: + handle_invalid_entry ERROR_INVALID_EL1h + +sync_invalid_el0_64: + handle_invalid_entry SYNC_INVALID_EL0_64 + +irq_invalid_el0_64: + handle_invalid_entry IRQ_INVALID_EL0_64 + +fiq_invalid_el0_64: + handle_invalid_entry FIQ_INVALID_EL0_64 + +error_invalid_el0_64: + handle_invalid_entry ERROR_INVALID_EL0_64 + +sync_invalid_el0_32: + handle_invalid_entry SYNC_INVALID_EL0_32 + +irq_invalid_el0_32: + handle_invalid_entry IRQ_INVALID_EL0_32 + +fiq_invalid_el0_32: + handle_invalid_entry FIQ_INVALID_EL0_32 + +error_invalid_el0_32: + handle_invalid_entry ERROR_INVALID_EL0_32 + +handle_el1_irq: + kernel_entry + bl handle_irq + kernel_exit + +.globl err_hang +err_hang: b err_hang diff --git a/part15a-tcpip-webserver-gpio-interrupt/kernel/kernel.c b/part15a-tcpip-webserver-gpio-interrupt/kernel/kernel.c new file mode 100644 index 0000000..2a8aa6d --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/kernel/kernel.c @@ -0,0 +1,244 @@ +#include "../include/fb.h" +#include "../include/spi.h" +#include "../net/enc28j60.h" +#include "../tcpip/ip_arp_udp_tcp.h" +#include "kernel.h" + +// HELPER FUNCTIONS + +void *memset(void *dest, unsigned char val, unsigned short len) +{ + uint8_t *ptr = dest; + while (len-- > 0) + *ptr++ = val; + return dest; +} + +void *memcpy(void *dest, const void *src, unsigned short len) +{ + uint8_t *d = dest; + const uint8_t *s = src; + while (len--) + *d++ = *s++; + return dest; +} + +uint8_t memcmp(void *str1, void *str2, unsigned count) +{ + uint8_t *s1 = str1; + uint8_t *s2 = str2; + + while (count-- > 0) + { + if (*s1++ != *s2++) + return s1[-1] < s2[-1] ? -1 : 1; + } + + return 0; +} + +int strncmp(const char *s1, const char *s2, unsigned short n) +{ + unsigned char u1, u2; + + while (n-- > 0) + { + u1 = (unsigned char) *s1++; + u2 = (unsigned char) *s2++; + if (u1 != u2) return u1 - u2; + if (u1 == '\0') return 0; + } + + return 0; +} + +// NETWORKING GLOBALS AND FUNCTIONS + +ENC_HandleTypeDef handle; + +// MAC address to be assigned to the ENC28J60 +unsigned char myMAC[6] = { 0xb0, 0x07, 0x10, 0xad, 0xca, 0x11 }; + +// IP address to be assigned to the ENC28J60 +unsigned char deviceIP[4] = { 10, 7, 3, 20 }; + +void init_network(void) +{ + handle.Init.DuplexMode = ETH_MODE_HALFDUPLEX; + handle.Init.MACAddr = myMAC; + handle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; + handle.Init.InterruptEnableBits = EIE_LINKIE | EIE_PKTIE; + + debugstr("Starting network up."); + debugcrlf(); + if (!ENC_Start(&handle)) { + debugstr("Could not initialise network card."); + } else { + debugstr("Setting MAC address to b0:07:10:ad:ca:11."); + debugcrlf(); + + ENC_SetMacAddr(&handle); + + debugstr("Network card successfully initialised."); + } + debugcrlf(); + + debugstr("Waiting for ifup... "); + while (!(handle.LinkStatus & PHSTAT2_LSTAT)) ENC_IRQHandler(&handle); + debugstr("done."); + debugcrlf(); + + // Re-enable global interrupts + ENC_EnableInterrupts(EIE_INTIE); + + debugstr("Initialising the TCP stack... "); + init_udp_or_www_server(myMAC, deviceIP); + debugstr("done."); + debugcrlf(); +} + +void enc28j60PacketSend(unsigned short buflen, void *buffer) { + if (ENC_RestoreTXBuffer(&handle, buflen) == 0) { + ENC_WriteBuffer((unsigned char *) buffer, buflen); + handle.transmitLength = buflen; + ENC_Transmit(&handle); + } +} + +void serve(void) +{ + while (1) { + while (!ENC_GetReceivedFrame(&handle)); + + uint8_t *buf = (uint8_t *)handle.RxFrameInfos.buffer; + uint16_t len = handle.RxFrameInfos.length; + uint16_t dat_p = packetloop_arp_icmp_tcp(buf, len); + + if (dat_p != 0) { + debugstr("Incoming web request... "); + + if (strncmp("GET ", (char *)&(buf[dat_p]), 4) != 0) { + debugstr("not GET"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n

ERROR

"); + } else { + if (strncmp("/ ", (char *)&(buf[dat_p+4]), 2) == 0) { + // just one web page in the "root directory" of the web server + debugstr("GET root"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n

Hello world!

"); + } else { + // just one web page not in the "root directory" of the web server + debugstr("GET not root"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n

Goodbye cruel world.

"); + } + } + + www_server_reply(buf, dat_p); // send web page data + debugcrlf(); + } + } +} +/* +// ISR for incoming packet + +debugstr("we are into isr now"); +// Re-enable Interrupts +ENC_IRQHandler(&handle); + +while (!ENC_GetReceivedFrame(&handle)); + +uint8_t *buf = (uint8_t *)handle.RxFrameInfos.buffer; +uint16_t len = handle.RxFrameInfos.length; +uint16_t dat_p = packetloop_arp_icmp_tcp(buf, len); + +if (dat_p != 0) { + debugstr("Incoming web request... "); + + if (strncmp("GET ", (char *)&(buf[dat_p]), 4) != 0) { + debugstr("not GET"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n

ERROR

"); + } else { + if (strncmp("/ ", (char *)&(buf[dat_p+4]), 2) == 0) { + // just one web page in the "root directory" of the web server + debugstr("GET root"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n

Hello world!

"); + } else { + // just one web page not in the "root directory" of the web server + debugstr("GET not root"); + dat_p = fill_tcp_data(buf, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n

Goodbye cruel world.

"); + } + } + + www_server_reply(buf, dat_p); // send web page data + debugcrlf(); +} + +*/ +// TIMER FUNCTIONS + +const unsigned int timer1_int = CLOCKHZ; +const unsigned int timer3_int = CLOCKHZ / 4; +unsigned int timer1_val = 0; +unsigned int timer3_val = 0; + +void timer_init() { + timer1_val = REGS_TIMER->counter_lo; + timer1_val += timer1_int; + REGS_TIMER->compare[1] = timer1_val; + + timer3_val = REGS_TIMER->counter_lo; + timer3_val += timer3_int; + REGS_TIMER->compare[3] = timer3_val; +} + +void handle_timer_1() { + timer1_val += timer1_int; + REGS_TIMER->compare[1] = timer1_val; + REGS_TIMER->control_status |= SYS_TIMER_IRQ_1; + + unsigned int progval = timer1_val / timer1_int; + if (progval <= 100) { + //drawProgress(2, progval); + } else { + debugstr("Timer 1 done."); + debugcrlf(); + } +} + +void handle_timer_3() { + timer3_val += timer3_int; + REGS_TIMER->compare[3] = timer3_val; + REGS_TIMER->control_status |= SYS_TIMER_IRQ_3; + + unsigned int progval = timer3_val / timer3_int; + if (progval <= 100);// drawProgress(3, progval); +} + +// MAIN FUNCTION + +void main(void) +{ + fb_init(); + // Init GPIO interupt + //init_GPIOinterrupt(); + + // Kick off the timers + + irq_init_vectors(); + enable_interrupt_controller(); + irq_barrier(); + irq_enable(); + timer_init(); + + + // Init network and serve web pages + + spi_init(); + init_network(); + + serve(); + + // Catch us if we fall + + while(1); + +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/kernel/kernel.h b/part15a-tcpip-webserver-gpio-interrupt/kernel/kernel.h new file mode 100644 index 0000000..9fc24e2 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/kernel/kernel.h @@ -0,0 +1,50 @@ +#define PERIPHERAL_BASE 0xFE000000 +#define CLOCKHZ 1000000 + +struct timer_regs { + volatile unsigned int control_status; + volatile unsigned int counter_lo; + volatile unsigned int counter_hi; + volatile unsigned int compare[4]; +}; + +#define REGS_TIMER ((struct timer_regs *)(PERIPHERAL_BASE + 0x00003000)) + +struct arm_irq_regs_2711 { + volatile unsigned int irq0_pending_0; + volatile unsigned int irq0_pending_1; + volatile unsigned int irq0_pending_2; + volatile unsigned int res0; + volatile unsigned int irq0_enable_0; + volatile unsigned int irq0_enable_1; + volatile unsigned int irq0_enable_2; + volatile unsigned int res1; + volatile unsigned int irq0_disable_0; + volatile unsigned int irq0_disable_1; + volatile unsigned int irq0_disable_2; +}; + +typedef struct arm_irq_regs_2711 arm_irq_regs; + +#define REGS_IRQ ((arm_irq_regs *)(PERIPHERAL_BASE + 0x0000B200)) + +enum vc_irqs { + SYS_TIMER_IRQ_0 = 1, + SYS_TIMER_IRQ_1 = 2, + SYS_TIMER_IRQ_2 = 4, + SYS_TIMER_IRQ_3 = 8, + AUX_IRQ = (1 << 29) +}; + +void irq_init_vectors(); +void irq_enable(); +void irq_barrier(); +void irq_disable(); +void enable_interrupt_controller(); +void disable_interrupt_controller(); + +void handle_timer_1(); +void handle_timer_3(); + +void enc28j60PacketSend(unsigned short buflen, void *buffer); +void *memcpy(void *dest, const void *src, unsigned short len); diff --git a/part15a-tcpip-webserver-gpio-interrupt/kernel/utils.S b/part15a-tcpip-webserver-gpio-interrupt/kernel/utils.S new file mode 100644 index 0000000..7a34152 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/kernel/utils.S @@ -0,0 +1,20 @@ +.globl irq_init_vectors +irq_init_vectors: + adr x0, vectors + msr vbar_el1, x0 + ret + +.globl irq_enable +irq_enable: + msr daifclr, #2 + ret + +.globl irq_disable +irq_disable: + msr daifset, #2 + ret + +.globl irq_barrier +irq_barrier: + dsb sy + ret diff --git a/part15a-tcpip-webserver-gpio-interrupt/lib/fb.c b/part15a-tcpip-webserver-gpio-interrupt/lib/fb.c new file mode 100644 index 0000000..2cc89f7 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/lib/fb.c @@ -0,0 +1,260 @@ +#include "../include/io.h" +#include "../include/mb.h" +#include "../include/terminal.h" + +unsigned int width, height, pitch, isrgb; +unsigned char *fb; + +int curx = 0; +int cury = 0; + +void fb_init() +{ + mbox[0] = 35*4; // Length of message in bytes + mbox[1] = MBOX_REQUEST; + + mbox[2] = MBOX_TAG_SETPHYWH; // Tag identifier + mbox[3] = 8; // Value size in bytes + mbox[4] = 0; + mbox[5] = 1920; // Value(width) + mbox[6] = 1080; // Value(height) + + mbox[7] = MBOX_TAG_SETVIRTWH; + mbox[8] = 8; + mbox[9] = 8; + mbox[10] = 1920; + mbox[11] = 1080; + + mbox[12] = MBOX_TAG_SETVIRTOFF; + mbox[13] = 8; + mbox[14] = 8; + mbox[15] = 0; // Value(x) + mbox[16] = 0; // Value(y) + + mbox[17] = MBOX_TAG_SETDEPTH; + mbox[18] = 4; + mbox[19] = 4; + mbox[20] = 32; // Bits per pixel + + mbox[21] = MBOX_TAG_SETPXLORDR; + mbox[22] = 4; + mbox[23] = 4; + mbox[24] = 1; // RGB + + mbox[25] = MBOX_TAG_GETFB; + mbox[26] = 8; + mbox[27] = 8; + mbox[28] = 4096; // FrameBufferInfo.pointer + mbox[29] = 0; // FrameBufferInfo.size + + mbox[30] = MBOX_TAG_GETPITCH; + mbox[31] = 4; + mbox[32] = 4; + mbox[33] = 0; // Bytes per line + + mbox[34] = MBOX_TAG_LAST; + + // Check call is successful and we have a pointer with depth 32 + if (mbox_call(MBOX_CH_PROP) && mbox[20] == 32 && mbox[28] != 0) { + mbox[28] &= 0x3FFFFFFF; // Convert GPU address to ARM address + width = mbox[10]; // Actual physical width + height = mbox[11]; // Actual physical height + pitch = mbox[33]; // Number of bytes per line + isrgb = mbox[24]; // Pixel order + fb = (unsigned char *)((long)mbox[28]); + } +} + +void drawPixel(int x, int y, unsigned char attr) +{ + int offs = (y * pitch) + (x * 4); + *((unsigned int*)(fb + offs)) = vgapal[attr & 0x0f]; +} + +void drawRect(int x1, int y1, int x2, int y2, unsigned char attr, int fill) +{ + int y=y1; + + while (y <= y2) { + int x=x1; + while (x <= x2) { + if ((x == x1 || x == x2) || (y == y1 || y == y2)) drawPixel(x, y, attr); + else if (fill) drawPixel(x, y, (attr & 0xf0) >> 4); + x++; + } + y++; + } +} + +void drawLine(int x1, int y1, int x2, int y2, unsigned char attr) +{ + int dx, dy, p, x, y; + + dx = x2-x1; + dy = y2-y1; + x = x1; + y = y1; + p = 2*dy-dx; + + while (x= 0) { + drawPixel(x,y,attr); + y++; + p = p+2*dy-2*dx; + } else { + drawPixel(x,y,attr); + p = p+2*dy; + } + x++; + } +} + +void drawCircle(int x0, int y0, int radius, unsigned char attr, int fill) +{ + int x = radius; + int y = 0; + int err = 0; + + while (x >= y) { + if (fill) { + drawLine(x0 - y, y0 + x, x0 + y, y0 + x, (attr & 0xf0) >> 4); + drawLine(x0 - x, y0 + y, x0 + x, y0 + y, (attr & 0xf0) >> 4); + drawLine(x0 - x, y0 - y, x0 + x, y0 - y, (attr & 0xf0) >> 4); + drawLine(x0 - y, y0 - x, x0 + y, y0 - x, (attr & 0xf0) >> 4); + } + drawPixel(x0 - y, y0 + x, attr); + drawPixel(x0 + y, y0 + x, attr); + drawPixel(x0 - x, y0 + y, attr); + drawPixel(x0 + x, y0 + y, attr); + drawPixel(x0 - x, y0 - y, attr); + drawPixel(x0 + x, y0 - y, attr); + drawPixel(x0 - y, y0 - x, attr); + drawPixel(x0 + y, y0 - x, attr); + + if (err <= 0) { + y += 1; + err += 2*y + 1; + } + + if (err > 0) { + x -= 1; + err -= 2*x + 1; + } + } +} + +void drawChar(unsigned char ch, int x, int y, unsigned char attr, int zoom) +{ + unsigned char *glyph = (unsigned char *)&font + (ch < FONT_NUMGLYPHS ? ch : 0) * FONT_BPG; + + for (int i=1;i<=(FONT_HEIGHT*zoom);i++) { + for (int j=0;j<(FONT_WIDTH*zoom);j++) { + unsigned char mask = 1 << (j/zoom); + unsigned char col = (*glyph & mask) ? attr & 0x0f : (attr & 0xf0) >> 4; + + drawPixel(x+j, y+i, col); + } + glyph += (i%zoom) ? 0 : FONT_BPL; + } +} + +void drawString(int x, int y, char *s, unsigned char attr, int zoom) +{ + while (*s) { + if (*s == '\r') { + x = 0; + } else if(*s == '\n') { + x = 0; y += (FONT_HEIGHT*zoom); + } else { + drawChar(*s, x, y, attr, zoom); + x += (FONT_WIDTH*zoom); + } + s++; + } +} + +void moveRect(int oldx, int oldy, int width, int height, int shiftx, int shifty, unsigned char attr) +{ + unsigned int newx = oldx + shiftx, newy = oldy + shifty; + unsigned int xcount = 0, ycount = 0; + unsigned int bitmap[width][height]; // This is very unsafe if it's too big for the stack... + unsigned int offs; + + // Save the bitmap + while (xcount < width) { + while (ycount < height) { + offs = ((oldy + ycount) * pitch) + ((oldx + xcount) * 4); + + bitmap[xcount][ycount] = *((unsigned int*)(fb + offs)); + ycount++; + } + ycount=0; + xcount++; + } + // Wipe it out with background colour + drawRect(oldx, oldy, oldx + width, oldy + width, attr, 1); + // Draw it again + for (int i=newx;i= 1920) { + curx = 0; cury += 8; + } + if (cury + 8 >= 1080) { + cury = 0; + } + drawString(curx, cury, str, 0x0f, 1); + curx += (strlen(str) * 8); +} + +void debugcrlf(void) { + curx = 0; cury += 8; +} + +void debugch(unsigned char b) { + unsigned int n; + int c; + for(c=4;c>=0;c-=4) { + n=(b>>c)&0xF; + n+=n>9?0x37:0x30; + debugstr((char *)&n); + } + debugstr(" "); +} + +void debughex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + n=(d>>c)&0xF; + n+=n>9?0x37:0x30; + debugstr((char *)&n); + } + debugstr(" "); +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/lib/io.c b/part15a-tcpip-webserver-gpio-interrupt/lib/io.c new file mode 100644 index 0000000..6a7b221 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/lib/io.c @@ -0,0 +1,229 @@ +#include "../include/io.h" + +// GPIO + +enum { + GPFSEL0 = PERIPHERAL_BASE + 0x200000, + GPSET0 = PERIPHERAL_BASE + 0x20001C, + GPCLR0 = PERIPHERAL_BASE + 0x200028, + GPPUPPDN0 = PERIPHERAL_BASE + 0x2000E4 +}; + +enum { + GPIO_MAX_PIN = 53, + GPIO_FUNCTION_OUT = 1, + GPIO_FUNCTION_ALT5 = 2, + GPIO_FUNCTION_ALT3 = 7, + GPIO_FUNCTION_ALT0 = 4 +}; + +enum { + Pull_None = 0, + Pull_Down = 1, // Are down and up the right way around? + Pull_Up = 2 +}; + +void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } +unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; } + +unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) { + unsigned int field_mask = (1 << field_size) - 1; + + if (pin_number > field_max) return 0; + if (value > field_mask) return 0; + + unsigned int num_fields = 32 / field_size; + unsigned int reg = base + ((pin_number / num_fields) * 4); + unsigned int shift = (pin_number % num_fields) * field_size; + + unsigned int curval = mmio_read(reg); + curval &= ~(field_mask << shift); + curval |= value << shift; + mmio_write(reg, curval); + + return 1; +} + +unsigned int gpio_set (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); } +unsigned int gpio_clear (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); } +unsigned int gpio_pull (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); } +unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); } + +void gpio_useAsAlt0(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT0); +} + +void gpio_useAsAlt3(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT3); +} + +void gpio_useAsAlt5(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT5); +} + +void gpio_initOutputPinWithPullNone(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_OUT); +} + +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff) { + if (onOrOff) { + gpio_set(pin_number, 1); + } else { + gpio_clear(pin_number, 1); + } +} + +// UART + +enum { + AUX_BASE = PERIPHERAL_BASE + 0x215000, + AUX_IRQ = AUX_BASE, + AUX_ENABLES = AUX_BASE + 4, + AUX_MU_IO_REG = AUX_BASE + 64, + AUX_MU_IER_REG = AUX_BASE + 68, + AUX_MU_IIR_REG = AUX_BASE + 72, + AUX_MU_LCR_REG = AUX_BASE + 76, + AUX_MU_MCR_REG = AUX_BASE + 80, + AUX_MU_LSR_REG = AUX_BASE + 84, + AUX_MU_MSR_REG = AUX_BASE + 88, + AUX_MU_SCRATCH = AUX_BASE + 92, + AUX_MU_CNTL_REG = AUX_BASE + 96, + AUX_MU_STAT_REG = AUX_BASE + 100, + AUX_MU_BAUD_REG = AUX_BASE + 104, + AUX_UART_CLOCK = 500000000, + UART_MAX_QUEUE = 16 * 1024 +}; + +#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1) + +unsigned char uart_output_queue[UART_MAX_QUEUE]; +unsigned int uart_output_queue_write = 0; +unsigned int uart_output_queue_read = 0; + +void uart_init() { + mmio_write(AUX_ENABLES, 1); //enable UART1 + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_CNTL_REG, 0); + mmio_write(AUX_MU_LCR_REG, 3); //8 bits + mmio_write(AUX_MU_MCR_REG, 0); + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts + mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200)); + gpio_useAsAlt5(14); + gpio_useAsAlt5(15); + mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX +} + +unsigned int uart_isOutputQueueEmpty() { + return uart_output_queue_read == uart_output_queue_write; +} + +unsigned int uart_isReadByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x01; } +unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; } + +unsigned char uart_readByte() { + while (!uart_isReadByteReady()); + return (unsigned char)mmio_read(AUX_MU_IO_REG); +} + +void uart_writeByteBlockingActual(unsigned char ch) { + while (!uart_isWriteByteReady()); + mmio_write(AUX_MU_IO_REG, (unsigned int)ch); +} + +void uart_loadOutputFifo() { + while (!uart_isOutputQueueEmpty() && uart_isWriteByteReady()) { + uart_writeByteBlockingActual(uart_output_queue[uart_output_queue_read]); + uart_output_queue_read = (uart_output_queue_read + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + } +} + +void uart_writeByteBlocking(unsigned char ch) { + unsigned int next = (uart_output_queue_write + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + + while (next == uart_output_queue_read) uart_loadOutputFifo(); + + uart_output_queue[uart_output_queue_write] = ch; + uart_output_queue_write = next; +} + +void uart_writeText(char *buffer) { + while (*buffer) { + if (*buffer == '\n') uart_writeByteBlockingActual('\r'); + uart_writeByteBlockingActual(*buffer++); + } +} + +void uart_drainOutputQueue() { + while (!uart_isOutputQueueEmpty()) uart_loadOutputFifo(); +} + +void uart_update() { + uart_loadOutputFifo(); + + if (uart_isReadByteReady()) { + unsigned char ch = uart_readByte(); + if (ch == '\r') uart_writeText("\n"); else uart_writeByteBlocking(ch); + } +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + // get highest tetrad + n=(d>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlockingActual(n); + } +} + +void uart_byte(unsigned char b) { + unsigned int n; + int c; + for(c=4;c>=0;c-=4) { + // get highest tetrad + n=(b>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlockingActual(n); + } + uart_writeByteBlockingActual(' '); +} + +// TIMER + +struct timer_regs { + volatile unsigned int control_status; + volatile unsigned int counter_lo; + volatile unsigned int counter_hi; + volatile unsigned int compare[4]; +}; + +#define REGS_TIMER ((struct timer_regs *)(PERIPHERAL_BASE + 0x00003000)) + +unsigned long HAL_GetTick(void) { + unsigned int hi = REGS_TIMER->counter_hi; + unsigned int lo = REGS_TIMER->counter_lo; + + //double check hi value didn't change after setting it... + if (hi != REGS_TIMER->counter_hi) { + hi = REGS_TIMER->counter_hi; + lo = REGS_TIMER->counter_lo; + } + + return ((unsigned long)hi << 32) | lo; +} + +void HAL_Delay(unsigned int ms) { + unsigned long start = HAL_GetTick(); + + while(HAL_GetTick() < start + (ms * 1000)); +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/lib/mb.c b/part15a-tcpip-webserver-gpio-interrupt/lib/mb.c new file mode 100644 index 0000000..65dbbb8 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/lib/mb.c @@ -0,0 +1,39 @@ +#include "../include/io.h" + +// The buffer must be 16-byte aligned as only the upper 28 bits of the address can be passed via the mailbox +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +enum { + VIDEOCORE_MBOX = (PERIPHERAL_BASE + 0x0000B880), + MBOX_READ = (VIDEOCORE_MBOX + 0x0), + MBOX_POLL = (VIDEOCORE_MBOX + 0x10), + MBOX_SENDER = (VIDEOCORE_MBOX + 0x14), + MBOX_STATUS = (VIDEOCORE_MBOX + 0x18), + MBOX_CONFIG = (VIDEOCORE_MBOX + 0x1C), + MBOX_WRITE = (VIDEOCORE_MBOX + 0x20), + MBOX_RESPONSE = 0x80000000, + MBOX_FULL = 0x80000000, + MBOX_EMPTY = 0x40000000 +}; + +unsigned int mbox_call(unsigned char ch) +{ + // 28-bit address (MSB) and 4-bit value (LSB) + unsigned int r = ((unsigned int)((long) &mbox) &~ 0xF) | (ch & 0xF); + + // Wait until we can write + while (mmio_read(MBOX_STATUS) & MBOX_FULL); + + // Write the address of our buffer to the mailbox with the channel appended + mmio_write(MBOX_WRITE, r); + + while (1) { + // Is there a reply? + while (mmio_read(MBOX_STATUS) & MBOX_EMPTY); + + // Is it a reply to our message? + if (r == mmio_read(MBOX_READ)) return mbox[1]==MBOX_RESPONSE; // Is it successful? + + } + return 0; +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/lib/multicore.c b/part15a-tcpip-webserver-gpio-interrupt/lib/multicore.c new file mode 100644 index 0000000..0000bcd --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/lib/multicore.c @@ -0,0 +1,44 @@ +#include "../include/multicore.h" + +void store32(unsigned long address, unsigned long value) +{ + *(unsigned long *) address = value; +} + +unsigned long load32(unsigned long address) +{ + return *(unsigned long *) address; +} + +void start_core1(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu1, (unsigned long)func); + asm volatile ("sev"); +} + +void start_core2(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu2, (unsigned long)func); + asm volatile ("sev"); +} + +void start_core3(void (*func)(void)) +{ + store32((unsigned long)&spin_cpu3, (unsigned long)func); + asm volatile ("sev"); +} + +void clear_core1(void) +{ + store32((unsigned long)&spin_cpu1, 0); +} + +void clear_core2(void) +{ + store32((unsigned long)&spin_cpu2, 0); +} + +void clear_core3(void) +{ + store32((unsigned long)&spin_cpu3, 0); +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/lib/spi.c b/part15a-tcpip-webserver-gpio-interrupt/lib/spi.c new file mode 100644 index 0000000..a03d564 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/lib/spi.c @@ -0,0 +1,100 @@ +#include "../include/io.h" +#include "../include/fb.h" + +// SPI + +struct Spi0Regs { + volatile unsigned int cs; + volatile unsigned int fifo; + volatile unsigned int clock; + volatile unsigned int data_length; + volatile unsigned int ltoh; + volatile unsigned int dc; +}; + +#define REGS_SPI0 ((struct Spi0Regs *)(PERIPHERAL_BASE + 0x00204000)) + +// CS Register +#define CS_LEN_LONG (1 << 25) +#define CS_DMA_LEN (1 << 24) +#define CS_CSPOL2 (1 << 23) +#define CS_CSPOL1 (1 << 22) +#define CS_CSPOL0 (1 << 21) +#define CS_RXF (1 << 20) +#define CS_RXR (1 << 19) +#define CS_TXD (1 << 18) +#define CS_RXD (1 << 17) +#define CS_DONE (1 << 16) +#define CS_LEN (1 << 13) +#define CS_REN (1 << 12) +#define CS_ADCS (1 << 11) +#define CS_INTR (1 << 10) +#define CS_INTD (1 << 9) +#define CS_DMAEN (1 << 8) +#define CS_TA (1 << 7) +#define CS_CSPOL (1 << 6) +#define CS_CLEAR_RX (1 << 5) +#define CS_CLEAR_TX (1 << 4) +#define CS_CPOL__SHIFT 3 +#define CS_CPHA__SHIFT 2 +#define CS_CS (1 << 0) +#define CS_CS__SHIFT 0 + +void spi_init() { + gpio_useAsAlt0(7); //CS1 + gpio_initOutputPinWithPullNone(8); //CS0 + gpio_useAsAlt0(9); //MISO + gpio_useAsAlt0(10); //MOSI + gpio_useAsAlt0(11); //SCLK +} + +void spi_chip_select(unsigned char chip_select) { + gpio_setPinOutputBool(8, chip_select); +} + +void spi_send_recv(unsigned char *sbuffer, unsigned char *rbuffer, unsigned int size) { + REGS_SPI0->data_length = size; + REGS_SPI0->cs = REGS_SPI0->cs | CS_CLEAR_RX | CS_CLEAR_TX | CS_TA; + + unsigned int read_count = 0; + unsigned int write_count = 0; + + while(read_count < size || write_count < size) { + while(write_count < size && REGS_SPI0->cs & CS_TXD) { + if (sbuffer) { + REGS_SPI0->fifo = *sbuffer++; + } else { + REGS_SPI0->fifo = 0; + } + + write_count++; + } + + while(read_count < size && REGS_SPI0->cs & CS_RXD) { + unsigned int data = REGS_SPI0->fifo; + + if (rbuffer) { + *rbuffer++ = data; + } + + read_count++; + } + } + + while(!(REGS_SPI0->cs & CS_DONE)) { + while(REGS_SPI0->cs & CS_RXD) { + unsigned int r = REGS_SPI0->fifo; + debughex(r); + } + } + + REGS_SPI0->cs = (REGS_SPI0->cs & ~CS_TA); +} + +void spi_send(unsigned char *data, unsigned int size) { + spi_send_recv(data, 0, size); +} + +void spi_recv(unsigned char *data, unsigned int size) { + spi_send_recv(0, data, size); +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/net/enc28j60.c b/part15a-tcpip-webserver-gpio-interrupt/net/enc28j60.c new file mode 100644 index 0000000..eab8a76 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/net/enc28j60.c @@ -0,0 +1,1453 @@ +/** + ****************************************************************************** + * @file enc28j60.c + * @author Christian Schoffit, portions from Gregory Nutt: + * Copyright (C) 2010-2012, 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * @version V1.0.0 + * @date 02-June-2015 + * @brief This file provides a set of functions needed to manage the ENC28J60 + * Stand-Alone Ethernet Controller with SPI Interface. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2015 Christian Schoffit

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Christian Schoffit nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* +Module Feature Issue Issue Summary Affected Revisions + B1 B4 B5 B7 +MAC Interface - 1. MAC registers unreliable with slow asynchronous SPI clock X X +Reset - 2. CLKRDY set early X X X X +Core Operating 3. Industrial (-40°C to +85°C) temperature range unsupported X X + Specifications +Oscillator CLKOUT pin 4. CLKOUT unavailable in Power Save mode X X X X +Memory Ethernet 5. Receive buffer must start at 0000h X X X X + Buffer +Interrupts - 6. Receive Packet Pending Interrupt Flag (PKTIF) unreliable X X X X +PHY - 7. TPIN+/- automatic polarity detection and correction + unreliable X X X X +PHY - 8. RBIAS resistor value differs between silicon revisions X X +PHY - 9. Internal loopback in half-duplex unreliable X X X X +PHY - 10. Internal loopback in full-duplex unreliable X X X X +PHY LEDs - 11. Combined Collision and Duplex Status mode unavailable X X X X +Transmit - 12. Transmit abort may stall transmit logic X X X X +Logic +PHY - 13. Received link pulses potentially cause collisions X X +Memory Ethernet 14. Even values in ERXRDPT may corrupt receive buffer X X X X + Buffer +Transmit - 15. LATECOL Status bit unreliable X X X X +Logic +PHY LEDs - 16. LED auto-polarity detection unreliable X X X X +DMA - 17. DMA checksum calculations will abort receive packets X X X X +Receive - 18. Pattern match filter allows reception of extra packets X X X X +Filter +SPI - 19. Reset command unavailable in Power Save mode X X X X +Interface + +Only workaround relative to issues affecting B7 silicon revision are implemented. Therefore, issues +specific to Ethernet conformance are not addressed, since they only affect B1 and B3 silicon revisions. + +Erratas 7, 8, 16... have workaround implemented by hardware + +Errata 18 is implemented in lwip stack +*/ + +/* Includes ------------------------------------------------------------------*/ +#include "enc28j60.h" +#include "../include/fb.h" +#include "../include/io.h" + +/** @addtogroup BSP + * @{ + */ + +/** @addtogroup Components + * @{ + */ + +/** @defgroup ENC28J60 + * @{ + */ + +/** @defgroup ENC28J60_Private_Types_Definitions + * @{ + */ + +/** @defgroup ENC28J60_Private_Defines + * @{ + */ + +/* Poll timeout */ + +#define ENC_POLLTIMEOUT 50 + +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Macros + * @{ + */ + +/* Packet Memory ************************************************************/ + +/* Packet memory layout */ + +#define ALIGNED_BUFSIZE ((CONFIG_NET_ETH_MTU + 255) & ~255) + +/* Work around Errata #5 (spurious reset of ERXWRPT to 0) by placing the RX + * FIFO at the beginning of packet memory. + */ + +# define PKTMEM_RX_START 0x0000 /* RX buffer must be at addr 0 for errata 5 */ +# define PKTMEM_RX_END (PKTMEM_END-ALIGNED_BUFSIZE) /* RX buffer length is total SRAM minus TX buffer */ +# define PKTMEM_TX_START (PKTMEM_RX_END+1) /* Start TX buffer after */ +# define PKTMEM_TX_ENDP1 (PKTMEM_TX_START+ALIGNED_BUFSIZE) /* Allow TX buffer for two frames */ + +/* Misc. Helper Macros ******************************************************/ + +#define enc_rdgreg(ctrlreg) \ + enc_rdgreg2(ENC_RCR | GETADDR(ctrlreg)) +#define enc_wrgreg(ctrlreg, wrdata) \ + enc_wrgreg2(ENC_WCR | GETADDR(ctrlreg), wrdata) +#define enc_bfcgreg(ctrlreg,clrbits) \ + enc_wrgreg2(ENC_BFC | GETADDR(ctrlreg), clrbits) +#define enc_bfsgreg(ctrlreg,setbits) \ + enc_wrgreg2(ENC_BFS | GETADDR(ctrlreg), setbits) + +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Variables + * @{ + */ + + /* Stores how many iterations the microcontroller can do in 1 µs */ +static uint32_t iter_per_us=0; + +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Function_Prototypes + * @{ + */ +/** + * @} + */ + +/** @defgroup ENC28J60_Private_Functions + * @{ + */ + +/** + Calibrate the constant time + **/ + +static void calibrate(void) +{ + uint32_t time; + volatile uint32_t i; + + iter_per_us = 1000000; + + time = HAL_GetTick(); + /* Wait for next tick */ + while (HAL_GetTick() == time) { + /* wait */ + } + for (i=0; ibank = 0; /* Initialize the trace on the current selected bank */ + //up_mdelay(2); + HAL_Delay(2); /* >1000 µs, conforms to errata #2 */ +} + +/**************************************************************************** + * Function: enc_setbank + * + * Description: + * Set the bank for these next control register access. + * + * Assumption: + * The caller has exclusive access to the SPI bus + * + * Parameters: + * handle - Reference to the driver state structure + * bank - The bank to select (0-3) + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void enc_setbank(ENC_HandleTypeDef *handle, uint8_t bank) { + + if (bank != handle->bank) { + /* Select bank 0 (just so that all of the bits are cleared) */ + + enc_bfcgreg(ENC_ECON1, ECON1_BSEL_MASK); + + /* Then OR in bits to get the correct bank */ + + if (bank != 0) + { + enc_bfsgreg(ENC_ECON1, (bank << ECON1_BSEL_SHIFT)); + } + + /* Then remember the bank setting */ + + handle->bank = bank; + } +} + +/**************************************************************************** + * Function: enc_rdbreg + * + * Description: + * Read from a banked control register using the RCR command. + * + * Parameters: + * handle - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to read + * + * Returned Value: + * The byte read from the banked register + * + * Assumptions: + * + ****************************************************************************/ + +static uint8_t enc_rdbreg(ENC_HandleTypeDef *handle, uint8_t ctrlreg) +{ + uint8_t data[3]; + + /* Set the bank */ + + enc_setbank(handle, GETBANK(ctrlreg)); + + /* Send the RCR command and collect the data. How we collect the data + * depends on if this is a PHY/CAN or not. The normal sequence requires + * 16-clocks: 8 to clock out the cmd and 8 to clock in the data. + */ + + data[0] = ENC_RCR | GETADDR(ctrlreg); + + /* The PHY/MAC sequence requires 24-clocks: 8 to clock out the cmd, + * 8 dummy bits, and 8 to clock in the PHY/MAC data. + */ + + ENC_SPI_SendBuf(data, data, (ISPHYMAC(ctrlreg))?3:2); + return (ISPHYMAC(ctrlreg))?data[2]:data[1]; +} + +/**************************************************************************** + * Function: enc_wrbreg + * + * Description: + * Write to a banked control register using the WCR command. Unlike + * reading, this same SPI sequence works for normal, MAC, and PHY + * registers. + * + * Parameters: + * handle - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to write + * wrdata - The data to send + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrbreg(ENC_HandleTypeDef *handle, uint8_t ctrlreg, + uint8_t wrdata) +{ + uint8_t data[2]; + + /* Set the bank */ + + enc_setbank(handle, GETBANK(ctrlreg)); + + /* Send the WCR command and data. The sequence requires 16-clocks: + * 8 to clock out the cmd + 8 to clock out the data. + */ + + data[0] = ENC_WCR | GETADDR(ctrlreg); + data[1] = wrdata; + + ENC_SPI_SendBuf(data, NULL, 2); +} + +/**************************************************************************** + * Function: enc_waitbreg + * + * Description: + * Wait until banked register bit(s) take a specific value (or a timeout + * occurs). + * + * Parameters: + * handle - Reference to the driver state structure + * ctrlreg - Bit encoded address of banked register to check + * bits - The bits to check (a mask) + * value - The value of the bits to return (value under mask) + * + * Returned Value: + * OK on success, negated errno on failure + * + * Assumptions: + * + ****************************************************************************/ + +static bool enc_waitbreg(ENC_HandleTypeDef *handle, uint8_t ctrlreg, + uint8_t bits, uint8_t value) +{ + uint32_t start = HAL_GetTick(); + uint32_t elapsed; + uint8_t rddata; + + /* Loop until the exit condition is met */ + + do + { + /* Read the byte from the requested banked register */ + + rddata = enc_rdbreg(handle, ctrlreg); + elapsed = HAL_GetTick() - start; + } + while ((rddata & bits) != value && elapsed < ENC_POLLTIMEOUT); + + return (rddata & bits) == value; +} + + +/**************************************************************************** + * Function: enc_rdphy + * + * Description: + * Read 16-bits of PHY data. + * + * Parameters: + * priv - Reference to the driver state structure + * phyaddr - The PHY register address + * + * Returned Value: + * 16-bit value read from the PHY + * + * Assumptions: + * + ****************************************************************************/ + +static uint16_t enc_rdphy(ENC_HandleTypeDef *handle, uint8_t phyaddr) +{ + uint16_t data = 0; + + /* "To read from a PHY register: + * + * 1. Write the address of the PHY register to read from into the MIREGADR + * register. + */ + + enc_wrbreg(handle, ENC_MIREGADR, phyaddr); + + /* 2. Set the MICMD.MIIRD bit. The read operation begins and the + * MISTAT.BUSY bit is set. + */ + + enc_wrbreg(handle, ENC_MICMD, MICMD_MIIRD); + + /* 3. Wait 10.24 µs. Poll the MISTAT.BUSY bit to be certain that the + * operation is complete. While busy, the host controller should not + * start any MIISCAN operations or write to the MIWRH register. + * + * When the MAC has obtained the register contents, the BUSY bit will + * clear itself. + */ + +// volatile int i; +// for (i=0; i<12*17; i++) { +// } + + up_udelay(12); + + if (enc_waitbreg(handle, ENC_MISTAT, MISTAT_BUSY, 0x00)) + { + /* 4. Clear the MICMD.MIIRD bit. */ + + enc_wrbreg(handle, ENC_MICMD, 0x00); + + /* 5. Read the desired data from the MIRDL and MIRDH registers. The + * order that these bytes are accessed is unimportant." + */ + + data = (uint16_t)enc_rdbreg(handle, ENC_MIRDL); + data |= (uint16_t)enc_rdbreg(handle, ENC_MIRDH) << 8; + } + + return data; +} + +/**************************************************************************** + * Function: enc_wrphy + * + * Description: + * write 16-bits of PHY data. + * + * Parameters: + * handle - Reference to the driver state structure + * phyaddr - The PHY register address + * phydata - 16-bit data to write to the PHY + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_wrphy(ENC_HandleTypeDef *handle, uint8_t phyaddr, + uint16_t phydata) +{ + /* "To write to a PHY register: + * + * 1. Write the address of the PHY register to write to into the + * MIREGADR register. + */ + + enc_wrbreg(handle, ENC_MIREGADR, phyaddr); + + /* 2. Write the lower 8 bits of data to write into the MIWRL register. */ + + enc_wrbreg(handle, ENC_MIWRL, phydata); + + /* 3. Write the upper 8 bits of data to write into the MIWRH register. + * Writing to this register automatically begins the MIIM transaction, + * so it must be written to after MIWRL. The MISTAT.BUSY bit becomes + * set. + */ + + enc_wrbreg(handle, ENC_MIWRH, phydata >> 8); + + /* The PHY register will be written after the MIIM operation completes, + * which takes 10.24 µs. When the write operation has completed, the BUSY + * bit will clear itself. + * + * The host controller should not start any MIISCAN or MIIRD operations + * while busy." + */ + + /* wait for approx 12 µs */ +// volatile int i; +// for (i=0; i<12*17; i++) { +// } + + up_udelay(12); + enc_waitbreg(handle, ENC_MISTAT, MISTAT_BUSY, 0x00); +} + + +/**************************************************************************** + * Function: enc_pwrfull + * + * Description: + * When normal operation is desired, the host controller must perform + * a slightly modified procedure: + * + * 1. Wake-up by clearing ECON2.PWRSV. + * 2. Wait at least 300 µs for the PHY to stabilize. To accomplish the + * delay, the host controller may poll ESTAT.CLKRDY and wait for it + * to become set. + * 3. Restore receive capability by setting ECON1.RXEN. + * + * After leaving Sleep mode, there is a delay of many milliseconds + * before a new link is established (assuming an appropriate link + * partner is present). The host controller may wish to wait until + * the link is established before attempting to transmit any packets. + * The link status can be determined by polling the PHSTAT2.LSTAT bit. + * Alternatively, the link change interrupt may be used if it is + * enabled. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +/* Power save mode not used (fix errata 4 and 19) */ +#if 0 +static void enc_pwrfull(ENC_HandleTypeDef *handle) +{ + /* 1. Wake-up by clearing ECON2.PWRSV. */ + + enc_bfcgreg(ENC_ECON2, ECON2_PWRSV); + + /* 2. Wait at least 300 µs for the PHY to stabilize. To accomplish the + * delay, the host controller may poll ESTAT.CLKRDY and wait for it to + * become set. + */ + + /* wait for approx 350 µs */ +// volatile int i; +// for (i=0; i<350*17; i++) { +// } + + up_udelay(350); + + enc_waitbreg(handle, ENC_ESTAT, ESTAT_CLKRDY, ESTAT_CLKRDY); + + /* 3. Restore receive capability by setting ECON1.RXEN. + * + * The caller will do this when it is ready to receive packets + */ +} +#endif + +/** + * @brief Initialize the enc28j60 and configure the needed hardware resources + * @param handle: Handle on data configuration. + * @retval None + */ +bool ENC_Start(ENC_HandleTypeDef *handle) +{ + /* register value */ + uint8_t regval; + + /* Calibrate time constant */ + calibrate(); + + /* System reset */ + enc_reset(handle); + + /* Use bank 0 */ + enc_setbank(handle, 0); + + /* Check if we are actually communicating with the ENC28J60. If its + * 0x00 or 0xff, then we are probably not communicating correctly + * via SPI. + */ + + regval = enc_rdbreg(handle, ENC_EREVID); + if (regval == 0x00 || regval == 0xff) { + return false; + } + + /* Initialize ECON2: Enable address auto increment. + */ + + enc_wrgreg(ENC_ECON2, ECON2_AUTOINC /* | ECON2_VRPS*/); + + /* Initialize receive buffer. + * First, set the receive buffer start address. + */ + + handle->nextpkt = PKTMEM_RX_START; + enc_wrbreg(handle, ENC_ERXSTL, PKTMEM_RX_START & 0xff); + enc_wrbreg(handle, ENC_ERXSTH, PKTMEM_RX_START >> 8); + + /* Set the receive data pointer */ + + /* Errata 14 */ + enc_wrbreg(handle, ENC_ERXRDPTL, PKTMEM_RX_END & 0xff); + enc_wrbreg(handle, ENC_ERXRDPTH, PKTMEM_RX_END >> 8); +/* + enc_wrbreg(handle, ENC_ERXRDPTL, PKTMEM_RX_START & 0xff); + enc_wrbreg(handle, ENC_ERXRDPTH, PKTMEM_RX_START >> 8); +*/ + + /* Set the receive buffer end. */ + + enc_wrbreg(handle, ENC_ERXNDL, PKTMEM_RX_END & 0xff); + enc_wrbreg(handle, ENC_ERXNDH, PKTMEM_RX_END >> 8); + + /* Set transmit buffer start. */ + + handle->transmitLength = 0; + enc_wrbreg(handle, ENC_ETXSTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(handle, ENC_ETXSTH, PKTMEM_TX_START >> 8); + + /* Set filter mode: unicast OR broadcast AND crc valid */ + + enc_wrbreg(handle, ENC_ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN); + + do { + HAL_Delay(10); /* Wait for 10 ms to let the clock be ready */ + regval = enc_rdbreg(handle, ENC_ESTAT); + } while ((regval & ESTAT_CLKRDY) == 0); + + /* Enable MAC receive */ + + enc_wrbreg(handle, ENC_MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS); + + /* Enable automatic padding and CRC operations */ + + if (handle->Init.DuplexMode == ETH_MODE_HALFDUPLEX) { + enc_wrbreg(handle, ENC_MACON3, + ((handle->Init.ChecksumMode == ETH_CHECKSUM_BY_HARDWARE)?MACON3_PADCFG0 | MACON3_TXCRCEN:0) | + MACON3_FRMLNEN); + enc_wrbreg(handle, ENC_MACON4, MACON4_DEFER); /* Defer transmission enable */ + + /* Set Non-Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MAIPGL, 0x12); + enc_wrbreg(handle, ENC_MAIPGH, 0x0c); + + /* Set Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MABBIPG, 0x12); + } else { + /* Set filter mode: unicast OR broadcast AND crc valid AND Full Duplex */ + + enc_wrbreg(handle, ENC_MACON3, + ((handle->Init.ChecksumMode == ETH_CHECKSUM_BY_HARDWARE)?MACON3_PADCFG0 | MACON3_TXCRCEN:0) | + MACON3_FRMLNEN | MACON3_FULDPX); + + /* Set Non-Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MAIPGL, 0x12); + + /* Set Back-to-Back Inter-Packet Gap */ + + enc_wrbreg(handle, ENC_MABBIPG, 0x15); + } + + /* Set the maximum packet size which the controller will accept */ + + enc_wrbreg(handle, ENC_MAMXFLL, (CONFIG_NET_ETH_MTU+18) & 0xff); + enc_wrbreg(handle, ENC_MAMXFLH, (CONFIG_NET_ETH_MTU+18) >> 8); + + /* Configure LEDs (No, just use the defaults for now) */ + /* enc_wrphy(priv, ENC_PHLCON, ??); */ + + /* Setup up PHCON1 & 2 */ + + if (handle->Init.DuplexMode == ETH_MODE_HALFDUPLEX) { + enc_wrphy(handle, ENC_PHCON1, 0x00); + enc_wrphy(handle, ENC_PHCON2, PHCON2_HDLDIS); /* errata 9 workaround */ + } else { + enc_wrphy(handle, ENC_PHCON1, PHCON1_PDPXMD); /* errata 10 workaround */ + enc_wrphy(handle, ENC_PHCON2, 0x00); + } + + /* Not used Restore normal operation mode + enc_pwrfull(handle); */ + + /* Process interrupt settings */ + if (handle->Init.InterruptEnableBits & EIE_LINKIE) { + /* Enable link change interrupt in PHY module */ + enc_wrphy(handle, ENC_PHIE, PHIE_PGEIE | PHIE_PLNKIE); + } + + /* Since we not modify PHLCON register, we don't fall in errata 11 case */ + + /* Reset all interrupt flags */ + enc_bfcgreg(ENC_EIR, EIR_ALLINTS); + + regval = handle->Init.InterruptEnableBits; + if (regval) { + /* Ensure INTIE is set when at least an interruption is selected */ + regval |= EIE_INTIE; + } + /* Enable selected interrupts in ethernet controller module */ + enc_bfsgreg(ENC_EIE, regval); + + /* Enable the receiver */ + enc_bfsgreg(ENC_ECON1, ECON1_RXEN); + + return true; +} + +/** + * @} + */ + +/**************************************************************************** + * Function: ENC_SetMacAddr + * + * Description: + * Set the MAC address to the configured value. This is done after ifup + * or after a TX timeout. Note that this means that the interface must + * be down before configuring the MAC addr. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_SetMacAddr(ENC_HandleTypeDef *handle) +{ + /* Program the hardware with it's MAC address (for filtering). + * MAADR1 MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 + * MAADR2 MAC Address Byte 2 (MAADR<39:32>), OUI Byte 2 + * MAADR3 MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 + * MAADR4 MAC Address Byte 4 (MAADR<23:16>) + * MAADR5 MAC Address Byte 5 (MAADR<15:8>) + * MAADR6 MAC Address Byte 6 (MAADR<7:0>) + */ + + enc_wrbreg(handle, ENC_MAADR1, handle->Init.MACAddr[0]); + enc_wrbreg(handle, ENC_MAADR2, handle->Init.MACAddr[1]); + enc_wrbreg(handle, ENC_MAADR3, handle->Init.MACAddr[2]); + enc_wrbreg(handle, ENC_MAADR4, handle->Init.MACAddr[3]); + enc_wrbreg(handle, ENC_MAADR5, handle->Init.MACAddr[4]); + enc_wrbreg(handle, ENC_MAADR6, handle->Init.MACAddr[5]); +} + + +/**************************************************************************** + * Function: ENC_WriteBuffer + * + * Description: + * Write a buffer of data. + * + * Parameters: + * buffer - A pointer to the buffer to write from + * buflen - The number of bytes to write + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +void ENC_WriteBuffer(void *buffer, uint16_t buflen) +{ + /* Send the WBM command and copy the packet itself into the transmit + * buffer at the position of the EWRPT register. + */ + + /* Select ENC28J60 chip + * + * "The WBM command is started by lowering the CS pin. ..." + * We explicitly select the ENC28J60 chip because we have to transmits several pieces of + * information while keeping CS low + * + */ + + ENC_SPI_Select(true); + + /* Send the write buffer memory command (ignoring the response) + * + * "...The [3-bit]WBM opcode should then be sent to the ENC28J60, + * followed by the 5-bit constant, 1Ah." + */ + + + ENC_SPI_SendWithoutSelection(ENC_WBM); + + /* Send the buffer + * + * "... After the WBM command and constant are sent, the data to + * be stored in the memory pointed to by EWRPT should be shifted + * out MSb first to the ENC28J60. After 8 data bits are received, + * the Write Pointer will automatically increment if AUTOINC is + * set. The host controller can continue to provide clocks on the + * SCK pin and send data on the SI pin, without raising CS, to + * keep writing to the memory. In this manner, with AUTOINC + * enabled, it is possible to continuously write sequential bytes + * to the buffer memory without any extra SPI command + * overhead. + */ + + ENC_SPI_SendBuf(buffer, NULL, buflen); + + /* De-select ENC28J60 chip + * + * "The WBM command is terminated by bringing up the CS pin. ..." + * done in ENC_SPI_SendBuf callback + */ + +} + +/**************************************************************************** + * Function: enc_rdbuffer + * + * Description: + * Read a buffer of data. + * + * Parameters: + * buffer - A pointer to the buffer to read into + * buflen - The number of bytes to read + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +static void enc_rdbuffer(void *buffer, int16_t buflen) +{ + /* Select ENC28J60 chip */ + + ENC_SPI_Select(true); + + /* Send the read buffer memory command (ignoring the response) */ + + ENC_SPI_SendWithoutSelection(ENC_RBM); + + /* Then read the buffer data */ + + ENC_SPI_SendBuf(NULL, buffer, buflen); + + /* De-select ENC28J60 chip: done in ENC_SPI_SendBuf callback */ +} + +/**************************************************************************** + * Function: ENC_RestoreTXBuffer + * + * Description: + * Prepare TX buffer + * + * Parameters: + * handle - Reference to the driver state structure + * len - length of buffer + * + * Returned Value: + * ERR_OK 0 No error, everything OK. + * ERR_MEM -1 Out of memory error. + * ERR_TIMEOUT -3 Timeout. + * + * Assumptions: + * + ****************************************************************************/ + +int8_t ENC_RestoreTXBuffer(ENC_HandleTypeDef *handle, uint16_t len) +{ + uint16_t txend; + uint8_t control_write[2]; + + /* Wait while TX is busy */ + if (!enc_waitgreg(ENC_ECON1, ECON1_TXRTS, 0)) { + return ERR_TIMEOUT; + } + + /* Verify that the hardware is ready to send another packet. The driver + * starts a transmission process by setting ECON1.TXRTS. When the packet is + * finished transmitting or is aborted due to an error/cancellation, the + * ECON1.TXRTS bit will be cleared. + * + * NOTE: If we got here, then we have committed to sending a packet. + * higher level logic must have assured that TX-related interrupts are disabled. + */ + + /* Send the packet: address=priv->dev.d_buf, length=priv->dev.d_len */ + + /* Set transmit buffer start (is this necessary?). */ + + enc_wrbreg(handle, ENC_ETXSTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(handle, ENC_ETXSTH, PKTMEM_TX_START >> 8); + + /* Reset the write pointer to start of transmit buffer */ + + enc_wrbreg(handle, ENC_EWRPTL, PKTMEM_TX_START & 0xff); + enc_wrbreg(handle, ENC_EWRPTH, PKTMEM_TX_START >> 8); + + /* Set the TX End pointer based on the size of the packet to send. Note + * that the offset accounts for the control byte at the beginning the + * buffer plus the size of the packet data. + */ + + txend = PKTMEM_TX_START + len; + + if (txend+8>PKTMEM_TX_ENDP1) { + return ERR_MEM; + } + + enc_wrbreg(handle, ENC_ETXNDL, txend & 0xff); + enc_wrbreg(handle, ENC_ETXNDH, txend >> 8); + + /* Send the write buffer memory command (ignoring the response) + * + * "...The [3-bit]WBM opcode should then be sent to the ENC28J60, + * followed by the 5-bit constant, 1Ah." + * + * "...the ENC28J60 requires a single per packet control byte to + * precede the packet for transmission." + * + * POVERRIDE: Per Packet Override bit (Not set): + * 1 = The values of PCRCEN, PPADEN and PHUGEEN will override the + * configuration defined by MACON3. + * 0 = The values in MACON3 will be used to determine how the packet + * will be transmitted + * PCRCEN: Per Packet CRC Enable bit (Set, but won't be used because + * POVERRIDE is zero). + * PPADEN: Per Packet Padding Enable bit (Set, but won't be used because + * POVERRIDE is zero). + * PHUGEEN: Per Packet Huge Frame Enable bit (Set, but won't be used + * because POVERRIDE is zero). + */ + + control_write[0] = ENC_WBM; + control_write[1] = PKTCTRL_PCRCEN | PKTCTRL_PPADEN | PKTCTRL_PHUGEEN; + ENC_SPI_SendBuf(control_write, control_write, 2); + + return ERR_OK; +} + +/**************************************************************************** + * Function: ENC_Transmit + * + * Description: + * Start hardware transmission. Called either from: + * + * - pkif interrupt when an application responds to the receipt of data + * by trying to send something, or + * - From watchdog based polling. + * + * Parameters: + * handle - Reference to the driver state structure + * len - length of buffer + * + * Returned Value: + * none + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef USE_PROTOTHREADS +PT_THREAD(ENC_Transmit(struct pt *pt, ENC_HandleTypeDef *handle)) +#else +void ENC_Transmit(ENC_HandleTypeDef *handle) +#endif +{ + PT_BEGIN(pt); + + if (handle->transmitLength != 0) { + /* A frame is ready for transmission */ + /* Set TXRTS to send the packet in the transmit buffer */ + + //enc_bfsgreg(ENC_ECON1, ECON1_TXRTS); + /* Implement erratas 12, 13 and 15 */ + /* Reset transmit logic */ + handle->retries = 16; + do { + enc_bfsgreg(ENC_ECON1, ECON1_TXRST); + enc_bfcgreg(ENC_ECON1, ECON1_TXRST); + enc_bfcgreg(ENC_EIR, EIR_TXERIF | EIR_TXIF); + + /* Start transmission */ + enc_bfsgreg(ENC_ECON1, ECON1_TXRTS); + +#ifdef USE_PROTOTHREADS + handle->startTime = HAL_GetTick(); + handle->duration = 20; /* Timeout after 20 ms */ + PT_WAIT_UNTIL(pt, (((enc_rdgreg(ENC_EIR) & (EIR_TXIF | EIR_TXERIF)) != 0) || + (HAL_GetTick() - handle->startTime > handle->duration))); +#else + /* Wait for end of transmission */ + enc_waitwhilegreg(ENC_EIR, EIR_TXIF | EIR_TXERIF, 0); +#endif + + HAL_Delay(20); // Added by AGB - fixes weird timing bug + + /* Stop transmission */ + enc_bfcgreg(ENC_ECON1, ECON1_TXRTS); + + { + uint16_t addtTsv4; + uint8_t tsv4, regval; + + /* read tsv */ + addtTsv4 = PKTMEM_TX_START + handle->transmitLength + 4; + + enc_wrbreg(handle, ENC_ERDPTL, addtTsv4 & 0xff); + enc_wrbreg(handle, ENC_ERDPTH, addtTsv4 >> 8); + + enc_rdbuffer(&tsv4, 1); + + regval = enc_rdgreg(ENC_EIR); + if (!(regval & EIR_TXERIF) || !(tsv4 & TSV_LATECOL)) { + break; + } + } + handle->retries--; + } while (handle->retries > 0); + /* Transmission finished (but can be unsuccessful) */ + handle->transmitLength = 0; + } + PT_END(pt); +} + +/**************************************************************************** + * Function: ENC_GetReceivedFrame + * + * Description: + * Check if we have received packet, and if so, retrieve them. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * true if new packet is available; false otherwise + * + * Assumptions: + * + ****************************************************************************/ + +bool ENC_GetReceivedFrame(ENC_HandleTypeDef *handle) +{ + uint8_t rsv[6]; + uint16_t pktlen; + uint16_t rxstat; + + uint8_t pktcnt; + + bool result = true; + + pktcnt = enc_rdbreg(handle, ENC_EPKTCNT); + if (pktcnt == 0) { + return false; + }; + + /* Set the read pointer to the start of the received packet (ERDPT) */ + + enc_wrbreg(handle, ENC_ERDPTL, (handle->nextpkt) & 0xff); + enc_wrbreg(handle, ENC_ERDPTH, (handle->nextpkt) >> 8); + + /* Read the next packet pointer and the 4 byte read status vector (RSV) + * at the beginning of the received packet. (ERDPT should auto-increment + * and wrap to the beginning of the read buffer as necessary) + */ + + enc_rdbuffer(rsv, 6); + + /* Decode the new next packet pointer, and the RSV. The + * RSV is encoded as: + * + * Bits 0-15: Indicates length of the received frame. This includes the + * destination address, source address, type/length, data, + * padding and CRC fields. This field is stored in little- + * endian format. + * Bits 16-31: Bit encoded RX status. + */ + + handle->nextpkt = (uint16_t)rsv[1] << 8 | (uint16_t)rsv[0]; + pktlen = (uint16_t)rsv[3] << 8 | (uint16_t)rsv[2]; + rxstat = (uint16_t)rsv[5] << 8 | (uint16_t)rsv[4]; + + /* Check if the packet was received OK */ + + if ((rxstat & RXSTAT_OK) == 0) { +#ifdef CONFIG_ENC28J60_STATS + priv->stats.rxnotok++; +#endif + result = false; + } else { /* Check for a usable packet length (4 added for the CRC) */ + if (pktlen > (CONFIG_NET_ETH_MTU + 4) || pktlen <= (ETH_HDRLEN + 4)) { + #ifdef CONFIG_ENC28J60_STATS + priv->stats.rxpktlen++; + #endif + result = false; + } else { /* Otherwise, read and process the packet */ + /* Save the packet length (without the 4 byte CRC) in handle->RxFrameInfos.length*/ + + handle->RxFrameInfos.length = pktlen - 4; + + /* Copy the data data from the receive buffer to priv->dev.d_buf. + * ERDPT should be correctly positioned from the last call to to + * end_rdbuffer (above). + */ + + enc_rdbuffer(handle->RxFrameInfos.buffer, handle->RxFrameInfos.length); + + } + } + + /* Move the RX read pointer to the start of the next received packet. + * This frees the memory we just read. + */ + + /* Errata 14 (on se sert de rxstat comme variable temporaire */ + rxstat = handle->nextpkt; + if (rxstat == PKTMEM_RX_START) { + rxstat = PKTMEM_RX_END; + } else { + rxstat--; + } + enc_wrbreg(handle, ENC_ERXRDPTL, rxstat & 0xff); + enc_wrbreg(handle, ENC_ERXRDPTH, rxstat >> 8); +/* + enc_wrbreg(handle, ENC_ERXRDPTL, (handle->nextpkt)); + enc_wrbreg(handle, ENC_ERXRDPTH, (handle->nextpkt) >> 8); +*/ + + /* Decrement the packet counter indicate we are done with this packet */ + + enc_bfsgreg(ENC_ECON2, ECON2_PKTDEC); + + return result; +} + +/**************************************************************************** + * Function: enc_linkstatus + * + * Description: + * The current link status can be obtained from the PHSTAT1.LLSTAT or + * PHSTAT2.LSTAT. + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static void enc_linkstatus(ENC_HandleTypeDef *handle) +{ + handle->LinkStatus = enc_rdphy(handle, ENC_PHSTAT2); +} + +/**************************************************************************** + * Function: ENC_EnableInterrupts + * + * Description: + * Enable individual ENC28J60 interrupts + * + * Parameters: + * bits - The individual bits to enable + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_EnableInterrupts(uint8_t bits) +{ + enc_bfsgreg(ENC_EIE, bits); +} + + +/**************************************************************************** + * Function: ENC_IRQHandler + * + * Description: + * Perform interrupt handling logic outside of the interrupt handler (on + * the work queue thread). + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_IRQHandler(ENC_HandleTypeDef *handle) +{ + uint8_t eir; + + /* Disable further interrupts by clearing the global interrupt enable bit. + * "After an interrupt occurs, the host controller should clear the global + * enable bit for the interrupt pin before servicing the interrupt. Clearing + * the enable bit will cause the interrupt pin to return to the non-asserted + * state (high). Doing so will prevent the host controller from missing a + * falling edge should another interrupt occur while the immediate interrupt + * is being serviced." + */ + + enc_bfcgreg(ENC_EIE, EIE_INTIE); + + /* Read EIR for interrupt flags + */ + + eir = enc_rdgreg(ENC_EIR) & EIR_ALLINTS; + + /* PKTIF is not reliable, check PKCNT instead */ + if (enc_rdbreg(handle, ENC_EPKTCNT) != 0) { + /* Manage EIR_PKTIF by software */ + eir |= EIR_PKTIF; + } + + /* Store interrupt flags in handle */ + handle->interruptFlags = eir; + + /* If link status has changed, read it */ + if ((eir & EIR_LINKIF) != 0) /* Link change interrupt */ + { + enc_linkstatus(handle); /* Get current link status */ + enc_rdphy(handle, ENC_PHIR); /* Clear the LINKIF interrupt */ + } + + /* Reset ENC28J60 interrupt flags, except PKTIF form which interruption is deasserted when PKTCNT reaches 0 */ + enc_bfcgreg(ENC_EIR, EIR_ALLINTS); + + /* Enable Ethernet interrupts */ + /* done after effective process on interrupts enc_bfsgreg(ENC_EIE, EIE_INTIE); */ +} + +/**************************************************************************** + * Function: ENC_GetPkcnt + * + * Description: + * Get the number of pending receive packets + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * the number of receive packet not processed yet + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_GetPkcnt(ENC_HandleTypeDef *handle) +{ + handle->pktCnt = enc_rdbreg(handle, ENC_EPKTCNT); +} + + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/part15a-tcpip-webserver-gpio-interrupt/net/enc28j60.h b/part15a-tcpip-webserver-gpio-interrupt/net/enc28j60.h new file mode 100644 index 0000000..e2c3504 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/net/enc28j60.h @@ -0,0 +1,811 @@ +/** + ****************************************************************************** + * @file enc28j60.h + * @author Christian Schoffit, portions from Gregory Nutt: + * Copyright (C) 2010-2012, 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * @version V1.0.0 + * @date 02-June-2015 + * @brief This file provides a set of functions needed to manage the ENC28J60 + * Stand-Alone Ethernet Controller with SPI Interface. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2015 Christian Schoffit

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Christian Schoffit nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +#ifndef ENC28J60_H_INCLUDED +#define ENC28J60_H_INCLUDED + +#include +#include + +#ifdef USE_PROTOTHREADS +#include "pt.h" +#else +#define PT_BEGIN(x) +#define PT_END(x) +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +/* Ethernet frames are between 64 and 1518 bytes long */ + +#define MIN_FRAMELEN 64 +#define MAX_FRAMELEN 1518 + +/* Callback functions *********************************************************/ + +/** + * Implement SPI Slave selection and deselection. Must be provided by user code + * param select: true if the ENC28J60 slave SPI if selected, false otherwise + * retval none + */ + +void ENC_SPI_Select(bool select); + +/** + * Implement SPI single byte send and receive. + * The ENC28J60 slave SPI must already be selected and wont be deselected after transmission + * Must be provided by user code + * param command: command or data to be sent to ENC28J60 + * retval answer from ENC28J60 + */ + +void ENC_SPI_SendWithoutSelection(uint8_t command); + +/** + * Implement SPI single byte send and receive. Must be provided by user code + * param command: command or data to be sent to ENC28J60 + * retval answer from ENC28J60 + */ + +void ENC_SPI_Send(uint8_t command); + +/** + * Implement SPI buffer send and receive. Must be provided by user code + * param master2slave: data to be sent from host to ENC28J60, can be NULL if we only want to receive data from slave + * param slave2master: answer from ENC28J60 to host, can be NULL if we only want to send data to slave + * retval none + */ + +void ENC_SPI_SendBuf(uint8_t *master2slave, uint8_t *slave2master, uint16_t bufferSize); + +/* Exported types ------------------------------------------------------------*/ +/** @defgroup ETH_Exported_Types ETH Exported Types + * @{ + */ + +/** + * @brief ETH Init Structure definition + */ + +typedef struct +{ + uint32_t DuplexMode; /*!< Selects the MAC duplex mode: Half-Duplex or Full-Duplex mode + This parameter can be a value of @ref ETH_Duplex_Mode */ + + uint8_t *MACAddr; /*!< MAC Address of used Hardware: must be pointer on an array of 6 bytes */ + + uint32_t ChecksumMode; /*!< Selects if the checksum is check by hardware or by software. + This parameter can be a value of @ref ETH_Checksum_Mode */ + + uint8_t InterruptEnableBits; /*!< Selects the enabled interrupts */ +} ENC_InitTypeDef; + + +/** + * @brief Received Frame Informations structure definition + */ +typedef struct +{ + uint32_t length; /*!< Frame length */ + + uint8_t buffer[MAX_FRAMELEN+20]; /*!< Frame buffer */ + +} ENC_RxFrameInfos; + + +/** + * @brief ENC28J60 Handle Structure definition + */ + +typedef struct +{ + ENC_InitTypeDef Init; /*!< Ethernet Init Configuration */ + + uint8_t bank; /*!< Currently selected bank */ + uint8_t interruptFlags;/*!< The last value of interrupts flags */ + uint8_t pktCnt; /*!< The number of pending receive packets */ + uint16_t nextpkt; /*!< Next packet address */ + uint16_t LinkStatus; /*!< Ethernet link status */ + uint16_t transmitLength;/*!< The length of ip frame to transmit */ + uint32_t startTime; /*!< The start time of the current timer */ + uint32_t duration; /*!< The duration of the current timer in ms */ + uint16_t retries; /*!< The number of transmission retries left to do */ + + ENC_RxFrameInfos RxFrameInfos; /*!< last Rx frame infos */ +} ENC_HandleTypeDef; + + /** + * @} + */ + +/* Exported constants --------------------------------------------------------*/ +/** @defgroup ETH_Exported_Constants ETH Exported Constants + * @{ + */ +/* Size of the Ethernet header */ +#define ETH_HDRLEN 14 /* Minimum size: 2*6 + 2 */ + + +#define ERR_OK 0 /* No error, everything OK. */ +#define ERR_MEM -1 /* Out of memory error. */ +#define ERR_BUF -2 /* Buffer error. */ +#define ERR_TIMEOUT -3 /* Timeout. */ + +/* ENC28J60 Commands ********************************************************/ +/* A total of seven instructions are implemented on the ENC28J60. Where: + * + * aaaaaa is the 5-bit address of a control register, and + * dddddddd is one or more bytes of data that may accompany the command. + */ + +#define ENC_RCR (0x00) /* Read Control Register + * 000 | aaaaa | (Register value returned)) */ +#define ENC_RBM (0x3a) /* Read Buffer Memory + * 001 | 11010 | (Read buffer data follows) */ +#define ENC_WCR (0x40) /* Write Control Register + * 010 | aaaaa | dddddddd */ +#define ENC_WBM (0x7a) /* Write Buffer Memory + * 011 | 11010 | (Write buffer data follows) */ +#define ENC_BFS (0x80) /* Bit Field Set + * 100 | aaaaa | dddddddd */ +#define ENC_BFC (0xa0) /* Bit Field Clear + * 101 | aaaaa | dddddddd */ +#define ENC_SRC (0xff) /* System Reset + * 111 | 11111 | (No data) */ + +/* Global Control Registers *************************************************/ +/* Control registers are accessed with the RCR, RBM, WCR, BFS, and BFC + * commands. The following identifies all ENC28J60 control registers. The + * control register memory is partitioned into four banks, selectable by the + * bank select bits, BSEL1:BSEL0, in the ECON1 register. + * + * The last five locations (0x1b to 0x1f) of all banks point to a common set + * of registers: EIE, EIR, ESTAT, ECON2 and ECON1. These are key registers + * used in controlling and monitoring the operation of the device. Their + * common mapping allows easy access without switching the bank. + * + * Control registers for the ENC28J60 are generically grouped as ETH, MAC and + * MII registers. Register names starting with E belong to the ETH group. + * Similarly, registers names starting with MA belong to the MAC group and + * registers prefixed with MI belong to the MII group. + */ + +#define ENC_EIE (0x1b) /* Ethernet Interrupt Enable Register */ +#define ENC_EIR (0x1c) /* Ethernet Interupt Request Register */ +#define ENC_ESTAT (0x1d) /* Ethernet Status Register */ +#define ENC_ECON2 (0x1e) /* Ethernet Control 2 Register */ +#define ENC_ECON1 (0x1f) /* Ethernet Control 1 Register */ + +/* Ethernet Interrupt Enable Register Bit Definitions */ + +#define EIE_RXERIE (1 << 0) /* Bit 0: Receive Error Interrupt Enable */ +#define EIE_TXERIE (1 << 1) /* Bit 1: Transmit Error Interrupt Enable */ + /* Bit 2: Reserved */ +#define EIE_TXIE (1 << 3) /* Bit 3: Transmit Enable */ +#define EIE_LINKIE (1 << 4) /* Bit 4: Link Status Change Interrupt Enable */ +#define EIE_DMAIE (1 << 5) /* Bit 5: DMA Interrupt Enable */ +#define EIE_PKTIE (1 << 6) /* Bit 6: Receive Packet Pending Interrupt Enable */ +#define EIE_INTIE (1 << 7) /* Bit 7: Global INT Interrupt Enable */ + +/* Ethernet Interrupt Request Register Bit Definitions */ + +#define EIR_RXERIF (1 << 0) /* Bit 0: Receive Error Interrupt */ +#define EIR_TXERIF (1 << 1) /* Bit 1: Transmit Error Interrupt */ + /* Bit 2: Reserved */ +#define EIR_TXIF (1 << 3) /* Bit 3: Transmit Interrupt */ +#define EIR_LINKIF (1 << 4) /* Bit 4: Link Change Interrupt */ +#define EIR_DMAIF (1 << 5) /* Bit 5: DMA Interrupt */ +#define EIR_PKTIF (1 << 6) /* Bit 6: Receive Packet Pending Interrupt */ + /* Bit 7: Reserved */ +#define EIR_ALLINTS (EIR_RXERIF | EIR_TXERIF | EIR_TXIF | EIR_LINKIF | EIR_DMAIF) /* All interrupts bar EIR_PKTIF*/ + +/* Ethernet Status Register Bit Definitions */ + +#define ESTAT_CLKRDY (1 << 0) /* Bit 0: Clock Ready */ +#define ESTAT_TXABRT (1 << 1) /* Bit 1: Transmit Abort Error */ +#define ESTAT_RXBUSY (1 << 2) /* Bit 2: Receive Busy */ + /* Bit 3: Reserved */ +#define ESTAT_LATECOL (1 << 4) /* Bit 4: Late Collision Error */ + /* Bit 5: Reserved */ +#define ESTAT_BUFER (1 << 6) /* Bit 6: Ethernet Buffer Error Status */ +#define ESTAT_INT (1 << 7) /* Bit 7: INT Interrupt */ + +/* Ethernet Control 1 Register Bit Definitions */ + +#define ECON1_BSEL_SHIFT (0) /* Bits 0-1: Bank select */ +#define ECON1_BSEL_MASK (3 << ECON1_BSEL_SHIFT) +# define ECON1_BSEL_BANK0 (0 << ECON1_BSEL_SHIFT) /* Bank 0 */ +# define ECON1_BSEL_BANK1 (1 << ECON1_BSEL_SHIFT) /* Bank 1 */ +# define ECON1_BSEL_BANK2 (2 << ECON1_BSEL_SHIFT) /* Bank 2 */ +# define ECON1_BSEL_BANK3 (3 << ECON1_BSEL_SHIFT) /* Bank 3 */ +#define ECON1_RXEN (1 << 2) /* Bit 2: Receive Enable */ +#define ECON1_TXRTS (1 << 3) /* Bit 3: Transmit Request to Send */ +#define ECON1_CSUMEN (1 << 4) /* Bit 4: DMA Checksum Enable */ +#define ECON1_DMAST (1 << 5) /* Bit 5: DMA Start and Busy Status */ +#define ECON1_RXRST (1 << 6) /* Bit 6: Receive Logic Reset */ +#define ECON1_TXRST (1 << 7) /* Bit 7: Transmit Logic Reset */ + +/* Ethernet Control 2 Register */ + /* Bits 0-2: Reserved */ +#define ECON2_VRPS (1 << 3) /* Bit 3: Voltage Regulator Power Save Enable */ + /* Bit 4: Reserved */ +#define ECON2_PWRSV (1 << 5) /* Bit 5: Power Save Enable */ +#define ECON2_PKTDEC (1 << 6) /* Bit 6: Packet Decrement */ +#define ECON2_AUTOINC (1 << 7) /* Bit 7: Automatic Buffer Pointer Increment Enable */ + +/* Banked Control Registers *************************************************/ +/* The remaining control registers are identified with a a 5 bit address and + * a bank selection. We pack the bank number and an indication if this is + * a MAC/PHY register access together with the control register address + * together to keep the design simpler. + */ + +#define ENC_ADDR_SHIFT (0) /* Bits 0-4: Register address */ +#define ENC_ADDR_MASK (0x1f << ENC_ADDR_SHIFT) +#define ENC_BANK_SHIFT (5) /* Bits 5-6: Bank number */ +#define ENC_BANK_MASK (3 << ENC_BSEL_SHIFT) +# define ENC_BANK0 (0 << ENC_BSEL_SHIFT) +# define ENC_BANK1 (1 << ENC_BSEL_SHIFT) +# define ENC_BANK2 (2 << ENC_BSEL_SHIFT) +# define ENC_BANK3 (3 << ENC_BSEL_SHIFT) +#define ENC_PHYMAC_SHIFT (7) /* Bit 7: This is a PHY/MAC command */ +#define ENC_PHYMAC (1 << ENC_PHYMAC_SHIFT) + +#define REGADDR(a,b,m) ((m) << ENC_PHYMAC_SHIFT | (b) << ENC_BANK_SHIFT | (a)) +#define GETADDR(a) ((a) & ENC_ADDR_MASK) +#define GETBANK(a) (((a) >> ENC_BANK_SHIFT) & 3) +#define ISPHYMAC(a) (((a) & ENC_PHYMAC) != 0) + +/* Bank 0 Control Register Addresses */ + +#define ENC_ERDPTL REGADDR(0x00, 0, 0) /* Read Pointer Low Byte (ERDPT<7:0> */ +#define ENC_ERDPTH REGADDR(0x01, 0, 0) /* Read Pointer High Byte (ERDPT<12:8>) */ +#define ENC_EWRPTL REGADDR(0x02, 0, 0) /* Write Pointer Low Byte (EWRPT<7:0>) */ +#define ENC_EWRPTH REGADDR(0x03, 0, 0) /* Write Pointer High Byte (EWRPT<12:8>) */ +#define ENC_ETXSTL REGADDR(0x04, 0, 0) /* TX Start Low Byte (ETXST<7:0>) */ +#define ENC_ETXSTH REGADDR(0x05, 0, 0) /* TX Start High Byte (ETXST<12:8>) */ +#define ENC_ETXNDL REGADDR(0x06, 0, 0) /* TX End Low Byte (ETXND<7:0>) */ +#define ENC_ETXNDH REGADDR(0x07, 0, 0) /* TX End High Byte (ETXND<12:8>) */ +#define ENC_ERXSTL REGADDR(0x08, 0, 0) /* RX Start Low Byte (ERXST<7:0>) */ +#define ENC_ERXSTH REGADDR(0x09, 0, 0) /* RX Start High Byte (ERXST<12:8>) */ +#define ENC_ERXNDL REGADDR(0x0a, 0, 0) /* RX End Low Byte (ERXND<7:0>) */ +#define ENC_ERXNDH REGADDR(0x0b, 0, 0) /* RX End High Byte (ERXND<12:8>) */ +#define ENC_ERXRDPTL REGADDR(0x0c, 0, 0) /* RX RD Pointer Low Byte (ERXRDPT<7:0>) */ +#define ENC_ERXRDPTH REGADDR(0x0d, 0, 0) /* RX RD Pointer High Byte (ERXRDPT<12:8>) */ +#define ENC_ERXWRPTL REGADDR(0x0e, 0, 0) /* RX WR Pointer Low Byte (ERXWRPT<7:0>) */ +#define ENC_ERXWRPTH REGADDR(0x0f, 0, 0) /* RX WR Pointer High Byte (ERXWRPT<12:8>) */ +#define ENC_EDMASTL REGADDR(0x10, 0, 0) /* DMA Start Low Byte (EDMAST<7:0>) */ +#define ENC_EDMASTH REGADDR(0x11, 0, 0) /* DMA Start High Byte (EDMAST<12:8>) */ +#define ENC_EDMANDL REGADDR(0x12, 0, 0) /* DMA End Low Byte (EDMAND<7:0>) */ +#define ENC_EDMANDH REGADDR(0x13, 0, 0) /* DMA End High Byte (EDMAND<12:8>) */ +#define ENC_EDMADSTL REGADDR(0x14, 0, 0) /* DMA Destination Low Byte (EDMADST<7:0>) */ +#define ENC_EDMADSTH REGADDR(0x15, 0, 0) /* DMA Destination High Byte (EDMADST<12:8>) */ +#define ENC_EDMACSL REGADDR(0x16, 0, 0) /* DMA Checksum Low Byte (EDMACS<7:0>) */ +#define ENC_EDMACSH REGADDR(0x17, 0, 0) /* DMA Checksum High Byte (EDMACS<15:8>) */ + /* 0x18-0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ +/* Bank 1 Control Register Addresses */ + +#define ENC_EHT0 REGADDR(0x00, 1, 0) /* Hash Table Byte 0 (EHT<7:0>) */ +#define ENC_EHT1 REGADDR(0x01, 1, 0) /* Hash Table Byte 1 (EHT<15:8>) */ +#define ENC_EHT2 REGADDR(0x02, 1, 0) /* Hash Table Byte 2 (EHT<23:16>) */ +#define ENC_EHT3 REGADDR(0x03, 1, 0) /* Hash Table Byte 3 (EHT<31:24>) */ +#define ENC_EHT4 REGADDR(0x04, 1, 0) /* Hash Table Byte 4 (EHT<39:32>) */ +#define ENC_EHT5 REGADDR(0x05, 1, 0) /* Hash Table Byte 5 (EHT<47:40>) */ +#define ENC_EHT6 REGADDR(0x06, 1, 0) /* Hash Table Byte 6 (EHT<55:48>) */ +#define ENC_EHT7 REGADDR(0x07, 1, 0) /* Hash Table Byte 7 (EHT<63:56>) */ +#define ENC_EPMM0 REGADDR(0x08, 1, 0) /* Pattern Match Mask Byte 0 (EPMM<7:0>) */ +#define ENC_EPMM1 REGADDR(0x09, 1, 0) /* Pattern Match Mask Byte 1 (EPMM<15:8>) */ +#define ENC_EPMM2 REGADDR(0x0a, 1, 0) /* Pattern Match Mask Byte 2 (EPMM<23:16>) */ +#define ENC_EPMM3 REGADDR(0x0b, 1, 0) /* Pattern Match Mask Byte 3 (EPMM<31:24>) */ +#define ENC_EPMM4 REGADDR(0x0c, 1, 0) /* Pattern Match Mask Byte 4 (EPMM<39:32>) */ +#define ENC_EPMM5 REGADDR(0x0d, 1, 0) /* Pattern Match Mask Byte 5 (EPMM<47:40>) */ +#define ENC_EPMM6 REGADDR(0x0e, 1, 0) /* Pattern Match Mask Byte 6 (EPMM<55:48>) */ +#define ENC_EPMM7 REGADDR(0x0f, 1, 0) /* Pattern Match Mask Byte 7 (EPMM<63:56>) */ +#define ENC_EPMCSL REGADDR(0x10, 1, 0) /* Pattern Match Checksum Low Byte (EPMCS<7:0>) */ +#define ENC_EPMCSH REGADDR(0x11, 1, 0) /* Pattern Match Checksum High Byte (EPMCS<15:0>) */ + /* 0x12-0x13: Reserved */ +#define ENC_EPMOL REGADDR(0x14, 1, 0) /* Pattern Match Offset Low Byte (EPMO<7:0>) */ +#define ENC_EPMOH REGADDR(0x15, 1, 0) /* Pattern Match Offset High Byte (EPMO<12:8>) */ + /* 0x16-0x17: Reserved */ +#define ENC_ERXFCON REGADDR(0x18, 1, 0) /* Receive Filter Configuration */ +#define ENC_EPKTCNT REGADDR(0x19, 1, 0) /* Ethernet Packet Count */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* Receive Filter Configuration Bit Definitions */ + +#define ERXFCON_BCEN (1 << 0) /* Bit 0: Broadcast Filter Enable */ +#define ERXFCON_MCEN (1 << 1) /* Bit 1: Multicast Filter Enable */ +#define ERXFCON_HTEN (1 << 2) /* Bit 2: Hash Table Filter Enable */ +#define ERXFCON_MPEN (1 << 3) /* Bit 3: Magic Packet Filter Enable */ +#define ERXFCON_PMEN (1 << 4) /* Bit 4: Pattern Match Filter Enable */ +#define ERXFCON_CRCEN (1 << 5) /* Bit 5: Post-Filter CRC Check Enable */ +#define ERXFCON_ANDOR (1 << 6) /* Bit 6: AND/OR Filter Select */ +#define ERXFCON_UCEN (1 << 7) /* Bit 7: Unicast Filter Enable */ + +/* Bank 2 Control Register Addresses */ + +#define ENC_MACON1 REGADDR(0x00, 2, 1) /* MAC Control 1 */ + /* 0x01: Reserved */ +#define ENC_MACON3 REGADDR(0x02, 2, 1) /* MAC Control 3 */ +#define ENC_MACON4 REGADDR(0x03, 2, 1) /* MAC Control 4 */ +#define ENC_MABBIPG REGADDR(0x04, 2, 1) /* Back-to-Back Inter-Packet Gap (BBIPG<6:0>) */ + /* 0x05: Reserved */ +#define ENC_MAIPGL REGADDR(0x06, 2, 1) /* Non-Back-to-Back Inter-Packet Gap Low Byte (MAIPGL<6:0>) */ +#define ENC_MAIPGH REGADDR(0x07, 2, 1) /* Non-Back-to-Back Inter-Packet Gap High Byte (MAIPGH<6:0>) */ +#define ENC_MACLCON1 REGADDR(0x08, 2, 1) /* MAC Collision Control 1 */ +#define ENC_MACLCON2 REGADDR(0x09, 2, 1) /* MAC Collision Control 2 */ +#define ENC_MAMXFLL REGADDR(0x0a, 2, 1) /* Maximum Frame Length Low Byte (MAMXFL<7:0>) */ +#define ENC_MAMXFLH REGADDR(0x0b, 2, 1) /* Maximum Frame Length High Byte (MAMXFL<15:8>) */ + /* 0x0c-0x11: Reserved */ +#define ENC_MICMD REGADDR(0x12, 2, 1) /* MII Command Register */ + /* 0x13: Reserved */ +#define ENC_MIREGADR REGADDR(0x14, 2, 1) /* MII Register Address */ + /* 0x15: Reserved */ +#define ENC_MIWRL REGADDR(0x16, 2, 1) /* MII Write Data Low Byte (MIWR<7:0>) */ +#define ENC_MIWRH REGADDR(0x17, 2, 1) /* MII Write Data High Byte (MIWR<15:8>) */ +#define ENC_MIRDL REGADDR(0x18, 2, 1) /* MII Read Data Low Byte (MIRD<7:0>) */ +#define ENC_MIRDH REGADDR(0x19, 2, 1) /* MII Read Data High Byte(MIRD<15:8>) */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON1_MARXEN (1 << 0) /* Bit 0: MAC Receive Enable */ +#define MACON1_PASSALL (1 << 1) /* Bit 1: Pass All Received Frames Enable */ +#define MACON1_RXPAUS (1 << 2) /* Bit 2: Pause Control Frame Reception Enable */ +#define MACON1_TXPAUS (1 << 3) /* Bit 3: Pause Control Frame Transmission Enable */ + /* Bits 4-7: Unimplemented or reserved */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON3_FULDPX (1 << 0) /* Bit 0: MAC Full-Duplex Enable */ +#define MACON3_FRMLNEN (1 << 1) /* Bit 1: Frame Length Checking Enable */ +#define MACON3_HFRMLEN (1 << 2) /* Bit 2: Huge Frame Enable */ +#define MACON3_PHDRLEN (1 << 3) /* Bit 3: Proprietary Header Enable */ +#define MACON3_TXCRCEN (1 << 4) /* Bit 4: Transmit CRC Enable */ +#define MACON3_PADCFG0 (1 << 5) /* Bit 5: Automatic Pad and CRC Configuration */ +#define MACON3_PADCFG1 (1 << 6) /* Bit 6: " " " " " " " " " " */ +#define MACON3_PADCFG2 (1 << 7) /* Bit 7: " " " " " " " " " " */ + +/* MAC Control 1 Register Bit Definitions */ + +#define MACON4_NOBKOFF (1 << 4) /* Bit 4: No Backoff Enable */ +#define MACON4_BPEN (1 << 5) /* Bit 5: No Backoff During Backpressure Enable */ +#define MACON4_DEFER (1 << 6) /* Bit 6: Defer Transmission Enable bit */ + +/* MII Command Register Bit Definitions */ + +#define MICMD_MIIRD (1 << 0) /* Bit 0: MII Read Enable */ +#define MICMD_MIISCAN (1 << 1) /* Bit 1: MII Scan Enable */ + +/* Bank 3 Control Register Addresses */ + +#define ENC_MAADR5 REGADDR(0x00, 3, 1) /* MAC Address Byte 5 (MAADR<15:8>) */ +#define ENC_MAADR6 REGADDR(0x01, 3, 1) /* MAC Address Byte 6 (MAADR<7:0>) */ +#define ENC_MAADR3 REGADDR(0x02, 3, 1) /* MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 */ +#define ENC_MAADR4 REGADDR(0x03, 3, 1) /* MAC Address Byte 4 (MAADR<23:16>) */ +#define ENC_MAADR1 REGADDR(0x04, 3, 1) /* MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 */ +#define ENC_MAADR2 REGADDR(0x05, 3, 1) /* MAC Address Byte 2 (MAADR<39:32>), OUI Byte 2 */ +#define ENC_EBSTSD REGADDR(0x06, 3, 0) /* Built-in Self-Test Fill Seed (EBSTSD<7:0>) */ +#define ENC_EBSTCON REGADDR(0x07, 3, 0) /* Built-in Self-Test Control */ +#define ENC_EBSTCSL REGADDR(0x08, 3, 0) /* Built-in Self-Test Checksum Low Byte (EBSTCS<7:0>) */ +#define ENC_EBSTCSH REGADDR(0x09, 3, 0) /* Built-in Self-Test Checksum High Byte (EBSTCS<15:8>) */ +#define ENC_MISTAT REGADDR(0x0a, 3, 1) /* MII Status Register */ + /* 0x0b-0x11: Reserved */ +#define ENC_EREVID REGADDR(0x12, 3, 0) /* Ethernet Revision ID */ + /* 0x13-0x14: Reserved */ +#define ENC_ECOCON REGADDR(0x15, 3, 0) /* Clock Output Control */ + /* 0x16: Reserved */ +#define ENC_EFLOCON REGADDR(0x17, 3, 0) /* Ethernet Flow Control */ +#define ENC_EPAUSL REGADDR(0x18, 3, 0) /* Pause Timer Value Low Byte (EPAUS<7:0>) */ +#define ENC_EPAUSH REGADDR(0x19, 3, 0) /* Pause Timer Value High Byte (EPAUS<15:8>) */ + /* 0x1a: Reserved */ + /* 0x1b-0x1f: EIE, EIR, ESTAT, ECON2, ECON1 */ + +/* Built-in Self-Test Control Register Bit Definitions */ + +#define EBSTCON_BISTST (1 << 0) /* Bit 0: Built-in Self-Test Start/Busy */ +#define EBSTCON_TME (1 << 1) /* Bit 1: Test Mode Enable */ +#define EBSTCON_TMSEL0 (1 << 2) /* Bit 2: Test Mode Select */ +#define EBSTCON_TMSEL1 (1 << 3) /* Bit 3: " " " " " " */ +#define EBSTCON_PSEL (1 << 4) /* Bit 4: Port Select */ +#define EBSTCON_PSV0 (1 << 5) /* Bit 5: Pattern Shift Value */ +#define EBSTCON_PSV1 (1 << 6) /* Bit 6: " " " " " */ +#define EBSTCON_PSV2 (1 << 7) /* Bit 7: " " " " " */ + +/* MII Status Register Register Bit Definitions */ + +#define MISTAT_BUSY (1 << 0) /* Bit 0: MII Management Busy */ +#define MISTAT_SCAN (1 << 1) /* Bit 1: MII Management Scan Operation */ +#define MISTAT_NVALID (1 << 2) /* Bit 2: MII Management Read Data Not Valid */ + /* Bits 3-7: Reserved or unimplemented */ + +/* Ethernet Flow Control Register Bit Definitions */ + +#define EFLOCON_FCEN0 (1 << 0) /* Bit 0: Flow Control Enable */ +#define EFLOCON_FCEN1 (1 << 1) /* Bit 1: " " " " " " */ +#define EFLOCON_FULDPXS (1 << 2) /* Bit 2: Read-Only MAC Full-Duplex Shadow */ + /* Bits 3-7: Reserved or unimplemented */ + +/* PHY Registers ************************************************************/ + +#define ENC_PHCON1 (0x00) /* PHY Control Register 1 */ +#define ENC_PHSTAT1 (0x01) /* PHY Status 1 */ +#define ENC_PHID1 (0x02) /* PHY ID Register 1 */ +#define ENC_PHID2 (0x03) /* PHY ID Register 2 */ +#define ENC_PHCON2 (0x10) /* PHY Control Register 2 */ +#define ENC_PHSTAT2 (0x11) /* PHY Status 2 */ +#define ENC_PHIE (0x12) /* PHY Interrupt Enable Register */ +#define ENC_PHIR (0x13) /* PHY Interrupt Request Register */ +#define ENC_PHLCON (0x14) + +/* PHY Control Register 1 Register Bit Definitions */ + +#define PHCON1_PDPXMD (1 << 8) /* Bit 8: PHY Duplex Mode */ +#define PHCON1_PPWRSV (1 << 11) /* Bit 11: PHY Power-Down */ +#define PHCON1_PLOOPBK (1 << 14) /* Bit 14: PHY Loopback */ +#define PHCON1_PRST (1 << 15) /* Bit 15: PHY Software Reset */ + +/* PHY Status 1 Register Bit Definitions */ + +#define PHSTAT1_JBSTAT (1 << 1) /* Bit 1: PHY Latching Jabber Status */ +#define PHSTAT1_LLSTAT (1 << 2) /* Bit 2: PHY Latching Link Status */ +#define PHSTAT1_PHDPX (1 << 11) /* Bit 11: PHY Half-Duplex Capable */ +#define PHSTAT1_PFDPX (1 << 12) /* Bit 12: PHY Full-Duplex Capable */ + +/* PHY Control Register 2 Register Bit Definitions */ + +#define PHCON2_HDLDIS (1 << 8) /* Bit 8: PHY Half-Duplex Loopback Disable */ +#define PHCON2_JABBER (1 << 10) /* Bit 10: Jabber Correction Disable */ +#define PHCON2_TXDIS (1 << 13) /* Bit 13: Twisted-Pair Transmitter Disable */ +#define PHCON2_FRCLINK (1 << 14) /* Bit 14: PHY Force Linkup */ + +/* PHY Status 2 Register Bit Definitions */ + +#define PHSTAT2_PLRITY (1 << 5) /* Bit 5: Polarity Status */ +#define PHSTAT2_DPXSTAT (1 << 9) /* Bit 9: PHY Duplex Status */ +#define PHSTAT2_LSTAT (1 << 10) /* Bit 10: PHY Link Status */ +#define PHSTAT2_COLSTAT (1 << 11) /* Bit 11: PHY Collision Status */ +#define PHSTAT2_RXSTAT (1 << 12) /* Bit 12: PHY Receive Status */ +#define PHSTAT2_TXSTAT (1 << 13) /* Bit 13: PHY Transmit Status */ + +/* PHY Interrupt Enable Register Bit Definitions */ + +#define PHIE_PGEIE (1 << 1) /* Bit 1: PHY Global Interrupt Enable */ +#define PHIE_PLNKIE (1 << 4) /* Bit 4: PHY Link Change Interrupt Enable */ + +/* PHIR Regiser Bit Definitions */ + +#define PHIR_PGIF (1 << 2) /* Bit 2: PHY Global Interrupt */ +#define PHIR_PLNKIF (1 << 4) /* Bit 4: PHY Link Change Interrupt */ + +/* PHLCON Regiser Bit Definitions */ + /* Bit 0: Reserved */ +#define PHLCON_STRCH (1 << 1) /* Bit 1: LED Pulse Stretching Enable */ +#define PHLCON_LFRQ0 (1 << 2) /* Bit 2: LED Pulse Stretch Time Configuration */ +#define PHLCON_LFRQ1 (1 << 3) /* Bit 3: " " " " " " " " " */ +#define PHLCON_LBCFG0 (1 << 4) /* Bit 4: LEDB Configuration */ +#define PHLCON_LBCFG1 (1 << 5) /* Bit 5: " " " " */ +#define PHLCON_LBCFG2 (1 << 6) /* Bit 6: " " " " */ +#define PHLCON_LBCFG3 (1 << 7) /* Bit 7: " " " " */ +#define PHLCON_LACFG0 (1 << 8) /* Bit 8: LEDA Configuration */ +#define PHLCON_LACFG1 (1 << 9) /* Bit 9: " " " " */ +#define PHLCON_LACFG2 (1 << 10) /* Bit 10: " " " " */ +#define PHLCON_LACFG3 (1 << 11) /* Bit 11: " " " " */ + +/* Packet Memory ************************************************************/ + +/* 8-Kbyte Transmit/Receive Packet Dual Port SRAM */ + +#define PKTMEM_START 0x0000 +#define PKTMEM_END 0x1fff + +/* maximum transfer unit */ +#define CONFIG_NET_ETH_MTU 1500 + +/* Packet Control Bits Definitions ******************************************/ + +#define PKTCTRL_POVERRIDE (1 << 0) /* Bit 0: Per Packet Override */ +#define PKTCTRL_PCRCEN (1 << 1) /* Bit 1: Per Packet CRC Enable */ +#define PKTCTRL_PPADEN (1 << 2) /* Bit 2: Per Packet Padding Enable */ +#define PKTCTRL_PHUGEEN (1 << 3) /* Bit 3: Per Packet Huge Frame Enable */ + +/* RX Status Bit Definitions ************************************************/ + +#define RXSTAT_LDEVENT (1 << 0) /* Bit 0: Long event or pack dropped */ + /* Bit 1: Reserved */ +#define RXSTAT_CEPS (1 << 2) /* Bit 2: Carrier event previously seen */ + /* Bit 3: Reserved */ +#define RXSTAT_CRCERROR (1 << 4) /* Bit 4: Frame CRC field bad */ +#define RXSTAT_LENERROR (1 << 5) /* Bit 5: Packet length != data length */ +#define RXSTAT_LENRANGE (1 << 6) /* Bit 6: Type/length field > 1500 bytes */ +#define RXSTAT_OK (1 << 7) /* Bit 7: Packet with valid CRC and no symbol errors */ +#define RXSTAT_MCAST (1 << 8) /* Bit 8: Packet with multicast address */ +#define RXSTAT_BCAST (1 << 9) /* Bit 9: Packet with broadcast address */ +#define RXSTAT_DRIBBLE (1 << 10) /* Bit 10: Additional bits received after packet */ +#define RXSTAT_CTRLFRAME (1 << 11) /* Bit 11: Control frame with valid type/length */ +#define RXSTAT_PAUSE (1 << 12) /* Bit 12: Control frame with pause frame opcde */ +#define RXSTAT_UNKOPCODE (1 << 13) /* Bit 13: Control frame with unknown opcode */ +#define RXSTAT_VLANTYPE (1 << 14) /* Bit 14: Current frame is a VLAN tagged frame */ + /* Bit 15: Zero */ +/* TSV bit definitions */ +#define TSV_LATECOL (1 << 5) /* Bit 5: Late Collision Error, RSV byte 3 */ + + + +/** @defgroup ETH_Duplex_Mode ETH Duplex Mode + * @{ + */ +#define ETH_MODE_FULLDUPLEX ((uint32_t)0x00000800) +#define ETH_MODE_HALFDUPLEX ((uint32_t)0x00000000) +/** + * @} + */ + +/** @defgroup ETH_Rx_Mode ETH Rx Mode + * @{ + */ +#define ETH_RXPOLLING_MODE ((uint32_t)0x00000000) +#define ETH_RXINTERRUPT_MODE ((uint32_t)0x00000001) +/** + * @} + */ + +/** @defgroup ETH_Checksum_Mode ETH Checksum Mode + * @{ + */ +#define ETH_CHECKSUM_BY_HARDWARE ((uint32_t)0x00000000) +#define ETH_CHECKSUM_BY_SOFTWARE ((uint32_t)0x00000001) +/** + * @} + */ + +/* Exported functions --------------------------------------------------------*/ +/** @addtogroup SPI_Exported_Functions + * @{ + */ + +/** @addtogroup SPI_Exported_Functions_Group1 + * @{ + */ +/* Initialization/de-initialization functions **********************************/ + + +/** + * Initialize the enc28j60 and configure the needed hardware resources + * param handle: Handle on data configuration. + * retval None + */ + +bool ENC_Start(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_SetMacAddr + * + * Description: + * Set the MAC address to the configured value. This is done after ifup + * or after a TX timeout. Note that this means that the interface must + * be down before configuring the MAC addr. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_SetMacAddr(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_RestoreTXBuffer + * + * Description: + * Prepare TX buffer + * + * Parameters: + * handle - Reference to the driver state structure + * len - length of buffer + * + * Returned Value: + * ERR_OK 0 No error, everything OK. + * ERR_MEM -1 Out of memory error. + * ERR_TIMEOUT -3 Timeout. + * + * Assumptions: + * + ****************************************************************************/ + +int8_t ENC_RestoreTXBuffer(ENC_HandleTypeDef *handle, uint16_t len); + +/**************************************************************************** + * Function: ENC_WriteBuffer + * + * Description: + * Write a buffer of data. + * + * Parameters: + * buffer - A pointer to the buffer to write from + * buflen - The number of bytes to write + * + * Returned Value: + * None + * + * Assumptions: + * Read pointer is set to the correct address + * + ****************************************************************************/ + +void ENC_WriteBuffer(void *buffer, uint16_t buflen); + +/**************************************************************************** + * Function: ENC_Transmit + * + * Description: + * Start hardware transmission. Called either from: + * + * - pkif interrupt when an application responds to the receipt of data + * by trying to send something, or + * - From watchdog based polling. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * none + * + * Assumptions: + * + ****************************************************************************/ + +#ifdef USE_PROTOTHREADS +PT_THREAD(ENC_Transmit(struct pt *pt, ENC_HandleTypeDef *handle)); +#else +void ENC_Transmit(ENC_HandleTypeDef *handle); +#endif + + /** + * @} + */ + + /** + * @} + */ + +/**************************************************************************** + * Function: ENC_GetReceivedFrame + * + * Description: + * Check if we have received packet, and if so, retrive them. + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * true if new packet is available; false otherwise + * + * Assumptions: + * + ****************************************************************************/ + +bool ENC_GetReceivedFrame(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_IRQHandler + * + * Description: + * Perform interrupt handling logic outside of the interrupt handler (on + * the work queue thread). + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_IRQHandler(ENC_HandleTypeDef *handle); + +/**************************************************************************** + * Function: ENC_EnableInterrupts + * + * Description: + * Enable individual ENC28J60 interrupts + * + * Parameters: + * bits - The individual bits to enable + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_EnableInterrupts(uint8_t bits); + +/**************************************************************************** + * Function: ENC_GetPkcnt + * + * Description: + * Get the number of pending receive packets + * + * Parameters: + * handle - Reference to the driver state structure + * + * Returned Value: + * the number of receive packet not processed yet + * + * Assumptions: + * + ****************************************************************************/ + +void ENC_GetPkcnt(ENC_HandleTypeDef *handle); + + +/**************************************************************************** + * Function: up_udelay + * + * Description: + * wait us µs + * + * Parameters: + * us - The amount of time to wait in µs + * + * Returned Value: + * none + * + * Assumptions: + * + ****************************************************************************/ + +void up_udelay(uint32_t us); + +#endif /* ENC28J60_H_INCLUDED */ diff --git a/part15a-tcpip-webserver-gpio-interrupt/net/encspi.c b/part15a-tcpip-webserver-gpio-interrupt/net/encspi.c new file mode 100644 index 0000000..524f73f --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/net/encspi.c @@ -0,0 +1,21 @@ +#include "../include/spi.h" + +void ENC_SPI_Select(unsigned char truefalse) { + spi_chip_select(!truefalse); // If it's true, select 0 (the ENC), if false, select 1 (i.e. deselect the ENC) +} + +void ENC_SPI_SendBuf(unsigned char *master2slave, unsigned char *slave2master, unsigned short bufferSize) { + spi_chip_select(0); + spi_send_recv(master2slave, slave2master, bufferSize); + spi_chip_select(1); // De-select the ENC +} + +void ENC_SPI_Send(unsigned char command) { + spi_chip_select(0); + spi_send(&command, 1); + spi_chip_select(1); // De-select the ENC +} + +void ENC_SPI_SendWithoutSelection(unsigned char command) { + spi_send(&command, 1); +} diff --git a/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_arp_udp_tcp.c b/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_arp_udp_tcp.c new file mode 100644 index 0000000..b0af8be --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_arp_udp_tcp.c @@ -0,0 +1,1528 @@ +/********************************************* + * vim:sw=8:ts=8:si:et + * To use the above modeline in vim you must have "set modeline" in your .vimrc + * + * Author: Guido Socher + * Copyright:LGPL V2 + * See http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html + * + * IP, Arp, UDP and TCP functions. + * + * The TCP implementation uses some size optimisations which are valid + * only if all data can be sent in one single packet. This is however + * not a big limitation for a microcontroller as you will anyhow use + * small web-pages. The web server must send the entire web page in one + * packet. The client "web browser" as implemented here can also receive + * large pages. + * + *********************************************/ +#include "net.h" +#include "../net/enc28j60.h" +#include "../kernel/kernel.h" +#include "../include/fb.h" +#include "ip_config.h" + +// I use them to debug stuff: +#define LEDOFF PORTB|=(1<>4)*4) +const char arpreqhdr[] ={0,1,8,0,6,4,0,1}; +#ifdef NTP_client +const char ntpreqhdr[] ={0xe3,0,4,0xfa,0,1,0,0,0,1}; +#endif + +// The Ip checksum is calculated over the ip header only starting +// with the header length field and a total length of 20 bytes +// unitl ip.dst +// You must set the IP checksum field to zero before you start +// the calculation. +// len for ip is 20. +// +// For UDP/TCP we do not make up the required pseudo header. Instead we +// use the ip.src and ip.dst fields of the real packet: +// The udp checksum calculation starts with the ip.src field +// Ip.src=4bytes,Ip.dst=4 bytes,Udp header=8bytes + data length=16+len +// In other words the len here is 8 + length over which you actually +// want to calculate the checksum. +// You must set the checksum field to zero before you start +// the calculation. +// The same algorithm is also used for udp and tcp checksums. +// len for udp is: 8 + 8 + data length +// len for tcp is: 4+4 + 20 + option len + data length +// +// For more information on how this algorithm works see: +// http://www.netfor2.com/checksum.html +// http://www.msc.uky.edu/ken/cs471/notes/chap3.htm +// The RFC has also a C code example: http://www.faqs.org/rfcs/rfc1071.html +uint16_t checksum(uint8_t *buf, uint16_t len,uint8_t type){ + // type 0=ip , icmp + // 1=udp + // 2=tcp + uint32_t sum = 0; + + //if(type==0){ + // // do not add anything, standard IP checksum as described above + // // Usable for ICMP and IP header + //} + if(type==1){ + sum+=IP_PROTO_UDP_V; // protocol udp + // the length here is the length of udp (data+header len) + // =length given to this function - (IP.scr+IP.dst length) + sum+=len-8; // = real udp len + } + if(type==2){ + sum+=IP_PROTO_TCP_V; + // the length here is the length of tcp (data+header len) + // =length given to this function - (IP.scr+IP.dst length) + sum+=len-8; // = real tcp len + } + // build the sum of 16bit words + while(len >1){ + sum += 0xFFFF & (((uint32_t)*buf<<8)|*(buf+1)); + buf+=2; + len-=2; + } + // if there is a byte left then add it (padded with zero) + if (len){ + sum += ((uint32_t)(0xFF & *buf))<<8; + } + // now calculate the sum over the bytes in the sum + // until the result is only 16bit long + while (sum>>16){ + sum = (sum & 0xFFFF)+(sum >> 16); + } + // build 1's complement: + return( (uint16_t) sum ^ 0xFFFF); +} + +void init_mac(uint8_t *mymac){ + if (mymac){ + memcpy(macaddr,mymac,6); + } +} + +#if defined (ALL_clients) +void client_ifconfig(uint8_t *ip,uint8_t *netmask) +{ + uint8_t i; + if (ip){ + i=0;while(i<4){ipaddr[i]=ip[i];i++;} + } + if (netmask){ + i=0;while(i<4){ipnetmask[i]=netmask[i];i++;} + } +} + +// returns 1 if destip must be routed via the GW. Returns 0 if destip is on the local LAN +uint8_t route_via_gw(uint8_t *destip) +{ + uint8_t i=0; + while(i<4){ + if ((destip[i] & ipnetmask[i]) != (ipaddr[i] & ipnetmask[i])){ + return(1); + } + i++; + } + return(0); +} +#endif + + +uint8_t check_ip_message_is_from(uint8_t *buf,uint8_t *ip) +{ + uint8_t i=0; + while(i<4){ + if(buf[IP_SRC_P+i]!=ip[i]){ + return(0); + } + i++; + } + return(1); +} + +uint8_t eth_type_is_arp_and_my_ip(uint8_t *buf,uint16_t len){ + uint8_t i=0; + // + if (len<41){ + return(0); + } + if(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V || + buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V){ + return(0); + } + while(i<4){ + if(buf[ETH_ARP_DST_IP_P+i] != ipaddr[i]){ + return(0); + } + i++; + } + return(1); +} + +uint8_t eth_type_is_ip_and_my_ip(uint8_t *buf,uint16_t len){ + uint8_t i=0; + //eth+ip+udp header is 42 + if (len<42){ + return(0); + } + if(buf[ETH_TYPE_H_P]!=ETHTYPE_IP_H_V || + buf[ETH_TYPE_L_P]!=ETHTYPE_IP_L_V){ + return(0); + } + if (buf[IP_HEADER_LEN_VER_P]!=0x45){ + // must be IP V4 and 20 byte header + return(0); + } + while(i<4){ + if(buf[IP_DST_P+i]!=ipaddr[i]){ + return(0); + } + i++; + } + return(1); +} + +// make a return eth header from a received eth packet +void make_eth(uint8_t *buf) +{ + uint8_t i=0; + // + //copy the destination mac from the source and fill my mac into src + while(i<6){ + buf[ETH_DST_MAC +i]=buf[ETH_SRC_MAC +i]; + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } +} +void fill_ip_hdr_checksum(uint8_t *buf) +{ + uint16_t ck; + // clear the 2 byte checksum + buf[IP_CHECKSUM_P]=0; + buf[IP_CHECKSUM_P+1]=0; + buf[IP_FLAGS_P]=0x40; // don't fragment + buf[IP_FLAGS_P+1]=0; // fragement offset + buf[IP_TTL_P]=64; // ttl + // calculate the checksum: + ck=checksum(&buf[IP_P], IP_HEADER_LEN,0); + buf[IP_CHECKSUM_P]=ck>>8; + buf[IP_CHECKSUM_P+1]=ck& 0xff; +} + +// make a return ip header from a received ip packet +void make_ip(uint8_t *buf) +{ + uint8_t i=0; + while(i<4){ + buf[IP_DST_P+i]=buf[IP_SRC_P+i]; + buf[IP_SRC_P+i]=ipaddr[i]; + i++; + } + fill_ip_hdr_checksum(buf); +} + +// swap seq and ack number and count ack number up +void step_seq(uint8_t *buf,uint16_t rel_ack_num,uint8_t cp_seq) +{ + uint8_t i; + uint8_t tseq; + i=4; + // sequence numbers: + // add the rel ack num to SEQACK + while(i>0){ + rel_ack_num=buf[TCP_SEQ_H_P+i-1]+rel_ack_num; + tseq=buf[TCP_SEQACK_H_P+i-1]; + buf[TCP_SEQACK_H_P+i-1]=0xff&rel_ack_num; + if (cp_seq){ + // copy the acknum sent to us into the sequence number + buf[TCP_SEQ_H_P+i-1]=tseq; + }else{ + buf[TCP_SEQ_H_P+i-1]= 0; // some preset value + } + rel_ack_num=rel_ack_num>>8; + i--; + } +} + +// make a return tcp header from a received tcp packet +// rel_ack_num is how much we must step the seq number received from the +// other side. We do not send more than 765 bytes of text (=data) in the tcp packet. +// No mss is included here. +// +// After calling this function you can fill in the first data byte at TCP_OPTIONS_P+4 +// If cp_seq=0 then an initial sequence number is used (should be use in synack) +// otherwise it is copied from the packet we received +void make_tcphead(uint8_t *buf,uint16_t rel_ack_num,uint8_t cp_seq) +{ + uint8_t i; + // copy ports: + i=buf[TCP_DST_PORT_H_P]; + buf[TCP_DST_PORT_H_P]=buf[TCP_SRC_PORT_H_P]; + buf[TCP_SRC_PORT_H_P]=i; + // + i=buf[TCP_DST_PORT_L_P]; + buf[TCP_DST_PORT_L_P]=buf[TCP_SRC_PORT_L_P]; + buf[TCP_SRC_PORT_L_P]=i; + step_seq(buf,rel_ack_num,cp_seq); + // zero the checksum + buf[TCP_CHECKSUM_H_P]=0; + buf[TCP_CHECKSUM_L_P]=0; + // no options: + // 20 bytes: + // The tcp header length is only a 4 bit field (the upper 4 bits). + // It is calculated in units of 4 bytes. + // E.g 20 bytes: 20/4=6 => 0x50=header len field + buf[TCP_HEADER_LEN_P]=0x50; +} + +void make_arp_answer_from_request(uint8_t *buf) +{ + uint8_t i=0; + // + make_eth(buf); + buf[ETH_ARP_OPCODE_H_P]=ETH_ARP_OPCODE_REPLY_H_V; + buf[ETH_ARP_OPCODE_L_P]=ETH_ARP_OPCODE_REPLY_L_V; + // fill the mac addresses: + while(i<6){ + buf[ETH_ARP_DST_MAC_P+i]=buf[ETH_ARP_SRC_MAC_P+i]; + buf[ETH_ARP_SRC_MAC_P+i]=macaddr[i]; + i++; + } + i=0; + while(i<4){ + buf[ETH_ARP_DST_IP_P+i]=buf[ETH_ARP_SRC_IP_P+i]; + buf[ETH_ARP_SRC_IP_P+i]=ipaddr[i]; + i++; + } + // eth+arp is 42 bytes: + enc28j60PacketSend(42,buf); +} + +void make_echo_reply_from_request(uint8_t *buf,uint16_t len) +{ + make_eth(buf); + make_ip(buf); + buf[ICMP_TYPE_P]=ICMP_TYPE_ECHOREPLY_V; + // we changed only the icmp.type field from request(=8) to reply(=0). + // we can therefore easily correct the checksum: + if (buf[ICMP_CHECKSUM_P] > (0xff-0x08)){ + buf[ICMP_CHECKSUM_P+1]++; + } + buf[ICMP_CHECKSUM_P]+=0x08; + // + enc28j60PacketSend(len,buf); +} + +// do some basic length calculations +uint16_t get_tcp_data_len(uint8_t *buf) +{ + int16_t i; + i=(((int16_t)buf[IP_TOTLEN_H_P])<<8)|(buf[IP_TOTLEN_L_P]&0xff); + i-=IP_HEADER_LEN; + i-=(buf[TCP_HEADER_LEN_P]>>4)*4; // generate len in bytes; + if (i<=0){ + i=0; + } + return((uint16_t)i); +} + + +// fill in tcp data at position pos. pos=0 means start of +// tcp data. Returns the position at which the string after +// this string could be filled. +uint16_t fill_tcp_data_p(uint8_t *buf,uint16_t pos, const uint8_t *progmem_s) +{ + char c; + // fill in tcp data at position pos + // + // with no options the data starts after the checksum + 2 more bytes (urgent ptr) + while ((c = *(progmem_s++))) { + buf[TCP_CHECKSUM_L_P+3+pos]=c; + pos++; + } + return(pos); +} + +// fill a binary string of len data into the tcp packet +uint16_t fill_tcp_data_len(uint8_t *buf,uint16_t pos, const uint8_t *s, uint8_t len) +{ + // fill in tcp data at position pos + // + // with no options the data starts after the checksum + 2 more bytes (urgent ptr) + while (len) { + buf[TCP_CHECKSUM_L_P+3+pos]=*s; + pos++; + s++; + len--; + } + return(pos); +} + +// fill in tcp data at position pos. pos=0 means start of +// tcp data. Returns the position at which the string after +// this string could be filled. +uint16_t fill_tcp_data(uint8_t *buf,uint16_t pos, const char *s) +{ + return(fill_tcp_data_len(buf,pos,(uint8_t*)s,strlen(s))); +} + +// Make just an ack packet with no tcp data inside +// This will modify the eth/ip/tcp header +void make_tcp_ack_from_any(uint8_t *buf,int16_t datlentoack,uint8_t addflags) +{ + uint16_t j; + make_eth(buf); + // fill the header: + buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V|addflags; + if (addflags==TCP_FLAGS_RST_V){ + make_tcphead(buf,datlentoack,1); + }else{ + if (datlentoack==0){ + // if there is no data then we must still acknoledge one packet + datlentoack=1; + } + // normal case, ack the data: + make_tcphead(buf,datlentoack,1); // no options + } + // total length field in the IP header must be set: + // 20 bytes IP + 20 bytes tcp (when no options) + j=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN; + buf[IP_TOTLEN_H_P]=j>>8; + buf[IP_TOTLEN_L_P]=j& 0xff; + make_ip(buf); + // use a low window size otherwise we have to have + // timers and can not just react on every packet. + buf[TCP_WIN_SIZE]=0x4; // 1024=0x400, 1280=0x500 2048=0x800 768=0x300 + buf[TCP_WIN_SIZE+1]=0; + // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + data len + j=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN,2); + buf[TCP_CHECKSUM_H_P]=j>>8; + buf[TCP_CHECKSUM_L_P]=j& 0xff; + enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+ETH_HEADER_LEN,buf); +} + + +// dlen is the amount of tcp data (http data) we send in this packet +// You can use this function only immediately after make_tcp_ack_from_any +// This is because this function will NOT modify the eth/ip/tcp header except for +// length and checksum +// You must set TCP_FLAGS before calling this +void make_tcp_ack_with_data_noflags(uint8_t *buf,uint16_t dlen) +{ + uint16_t j; + // total length field in the IP header must be set: + // 20 bytes IP + 20 bytes tcp (when no options) + len of data + j=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+dlen; + buf[IP_TOTLEN_H_P]=j>>8; + buf[IP_TOTLEN_L_P]=j& 0xff; + fill_ip_hdr_checksum(buf); + // zero the checksum + buf[TCP_CHECKSUM_H_P]=0; + buf[TCP_CHECKSUM_L_P]=0; + // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + data len + j=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN+dlen,2); + buf[TCP_CHECKSUM_H_P]=j>>8; + buf[TCP_CHECKSUM_L_P]=j& 0xff; + enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+dlen+ETH_HEADER_LEN,buf); +} + +#if defined (UDP_server) +// a udp server +void make_udp_reply_from_request_udpdat_ready(uint8_t *buf,uint16_t datalen,uint16_t port) +{ + uint16_t j; + make_eth(buf); + if (datalen>220){ + datalen=220; + } + // total length field in the IP header must be set: + j=IP_HEADER_LEN+UDP_HEADER_LEN+datalen; + buf[IP_TOTLEN_H_P]=j>>8; + buf[IP_TOTLEN_L_P]=j& 0xff; + make_ip(buf); + // send to port: + //buf[UDP_DST_PORT_H_P]=port>>8; + //buf[UDP_DST_PORT_L_P]=port & 0xff; + // sent to port of sender and use "port" as own source: + buf[UDP_DST_PORT_H_P]=buf[UDP_SRC_PORT_H_P]; + buf[UDP_DST_PORT_L_P]= buf[UDP_SRC_PORT_L_P]; + buf[UDP_SRC_PORT_H_P]=port>>8; + buf[UDP_SRC_PORT_L_P]=port & 0xff; + // calculte the udp length: + j=UDP_HEADER_LEN+datalen; + buf[UDP_LEN_H_P]=j>>8; + buf[UDP_LEN_L_P]=j& 0xff; + // zero the checksum + buf[UDP_CHECKSUM_H_P]=0; + buf[UDP_CHECKSUM_L_P]=0; + j=checksum(&buf[IP_SRC_P], 16 + datalen,1); + buf[UDP_CHECKSUM_H_P]=j>>8; + buf[UDP_CHECKSUM_L_P]=j& 0xff; + enc28j60PacketSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen,buf); +} + +// you can send a max of 220 bytes of data because we use only one +// byte for the data but udp messages are normally small. +void make_udp_reply_from_request(uint8_t *buf,char *data,uint8_t datalen,uint16_t port) +{ + uint8_t i=0; + // copy the data: + while(i>8)&0xff; + wwwport_l=(port&0xff); +} + +// this is for the server not the client: +void make_tcp_synack_from_syn(uint8_t *buf) +{ + uint16_t ck; + make_eth(buf); + // total length field in the IP header must be set: + // 20 bytes IP + 24 bytes (20tcp+4tcp options) + buf[IP_TOTLEN_H_P]=0; + buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4; + make_ip(buf); + buf[TCP_FLAGS_P]=TCP_FLAGS_SYNACK_V; + make_tcphead(buf,1,0); + // put an inital seq number + buf[TCP_SEQ_H_P+0]= 0; + buf[TCP_SEQ_H_P+1]= 0; + // we step only the second byte, this allows us to send packts + // with 255 bytes, 512 or 765 (step by 3) without generating + // overlapping numbers. + buf[TCP_SEQ_H_P+2]= seqnum; + buf[TCP_SEQ_H_P+3]= 0; + // step the inititial seq num by something we will not use + // during this tcp session: + seqnum+=3; + // add an mss options field with MSS to 1280: + // 1280 in hex is 0x500 + buf[TCP_OPTIONS_P]=2; + buf[TCP_OPTIONS_P+1]=4; + buf[TCP_OPTIONS_P+2]=0x05; + buf[TCP_OPTIONS_P+3]=0x0; + // The tcp header length is only a 4 bit field (the upper 4 bits). + // It is calculated in units of 4 bytes. + // E.g 24 bytes: 24/4=6 => 0x60=header len field + buf[TCP_HEADER_LEN_P]=0x60; + // here we must just be sure that the web browser contacting us + // will send only one get packet + buf[TCP_WIN_SIZE]=0x0a; // was 1400=0x578, 2560=0xa00 suggested by Andras Tucsni to be able to receive bigger packets + buf[TCP_WIN_SIZE+1]=0; // + // calculate the checksum, len=8 (start from ip.src) + TCP_HEADER_LEN_PLAIN + 4 (one option: mss) + ck=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN+4,2); + buf[TCP_CHECKSUM_H_P]=ck>>8; + buf[TCP_CHECKSUM_L_P]=ck& 0xff; + // add 4 for option mss: + enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4+ETH_HEADER_LEN,buf); +} + +// you must have initialized info_data_len at some time before calling this function +// +// This info_data_len initialisation is done automatically if you call +// packetloop_icmp_tcp(buf,enc28j60PacketReceive(BUFFER_SIZE, buf)); +// and test the return value for non zero. +// +// dlen is the amount of tcp data (http data) we send in this packet +// You can use this function only immediately after make_tcp_ack_from_any +// This is because this function will NOT modify the eth/ip/tcp header except for +// length and checksum +void www_server_reply(uint8_t *buf,uint16_t dlen) +{ + make_tcp_ack_from_any(buf,info_data_len,0); // send ack for http get + // fill the header: + // This code requires that we send only one data packet + // because we keep no state information. We must therefore set + // the fin here: + buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V|TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V; + make_tcp_ack_with_data_noflags(buf,dlen); // send data +} + +#endif // WWW_server + +#if defined (ALL_clients) || defined (GRATARP) || defined (WOL_client) +// fill buffer with a prog-mem string +void fill_buf_p(uint8_t *buf,uint16_t len, const char *progmem_str_p) +{ + while (len){ + *buf= pgm_read_byte(progmem_str_p); + buf++; + progmem_str_p++; + len--; + } +} +#endif + +#ifdef PING_client +// icmp echo, matchpat is a pattern that has to be sent back by the +// host answering the ping. +// The ping is sent to destip and mac dstmac +void client_icmp_request(uint8_t *buf,uint8_t *destip,uint8_t *dstmac) +{ + uint8_t i=0; + uint16_t ck; + // + while(i<6){ + buf[ETH_DST_MAC +i]=dstmac[i]; // gw mac in local lan or host mac + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } + buf[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; + buf[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; + fill_buf_p(&buf[IP_P],9,iphdr); + buf[IP_ID_L_P]=ipid; ipid++; + buf[IP_TOTLEN_L_P]=0x54; + buf[IP_PROTO_P]=IP_PROTO_ICMP_V; + i=0; + while(i<4){ + buf[IP_DST_P+i]=destip[i]; + buf[IP_SRC_P+i]=ipaddr[i]; + i++; + } + fill_ip_hdr_checksum(buf); + buf[ICMP_TYPE_P]=ICMP_TYPE_ECHOREQUEST_V; + buf[ICMP_TYPE_P+1]=0; // code + // zero the checksum + buf[ICMP_CHECKSUM_H_P]=0; + buf[ICMP_CHECKSUM_L_P]=0; + // a possibly unique id of this host: + buf[ICMP_IDENT_H_P]=5; // some number + buf[ICMP_IDENT_L_P]=ipaddr[3]; // last byte of my IP + // + buf[ICMP_IDENT_L_P+1]=0; // seq number, high byte + buf[ICMP_IDENT_L_P+2]=1; // seq number, low byte, we send only 1 ping at a time + // copy the data: + i=0; + while(i<56){ + buf[ICMP_DATA_P+i]=PINGPATTERN; + i++; + } + // + ck=checksum(&buf[ICMP_TYPE_P], 56+8,0); + buf[ICMP_CHECKSUM_H_P]=ck>>8; + buf[ICMP_CHECKSUM_L_P]=ck& 0xff; + enc28j60PacketSend(98,buf); +} +#endif // PING_client + + +#ifdef NTP_client +// ntp udp packet +// See http://tools.ietf.org/html/rfc958 for details +// +void client_ntp_request(uint8_t *buf,uint8_t *ntpip,uint8_t srcport,uint8_t *dstmac) +{ + uint8_t i=0; + uint16_t ck; + if (!enc28j60linkup())return; + // + while(i<6){ + buf[ETH_DST_MAC +i]=dstmac[i]; // gw mac in local lan or host mac + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } + buf[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; + buf[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; + fill_buf_p(&buf[IP_P],9,iphdr); + buf[IP_ID_L_P]=ipid; ipid++; + buf[IP_TOTLEN_L_P]=0x4c; + buf[IP_PROTO_P]=IP_PROTO_UDP_V; + i=0; + while(i<4){ + buf[IP_DST_P+i]=ntpip[i]; + buf[IP_SRC_P+i]=ipaddr[i]; + i++; + } + fill_ip_hdr_checksum(buf); + buf[UDP_DST_PORT_H_P]=0; + buf[UDP_DST_PORT_L_P]=0x7b; // ntp=123 + buf[UDP_SRC_PORT_H_P]=10; + buf[UDP_SRC_PORT_L_P]=srcport; // lower 8 bit of src port + buf[UDP_LEN_H_P]=0; + buf[UDP_LEN_L_P]=56; // fixed len + // zero the checksum + buf[UDP_CHECKSUM_H_P]=0; + buf[UDP_CHECKSUM_L_P]=0; + // copy the data: + i=0; + // most fields are zero, here we zero everything and fill later + while(i<48){ + buf[UDP_DATA_P+i]=0; + i++; + } + fill_buf_p(&buf[UDP_DATA_P],10,ntpreqhdr); + // + ck=checksum(&buf[IP_SRC_P], 16 + 48,1); + buf[UDP_CHECKSUM_H_P]=ck>>8; + buf[UDP_CHECKSUM_L_P]=ck& 0xff; + enc28j60PacketSend(90,buf); +} +// process the answer from the ntp server: +// if dstport==0 then accept any port otherwise only answers going to dstport +// return 1 on sucessful processing of answer +uint8_t client_ntp_process_answer(uint8_t *buf,uint32_t *time,uint8_t dstport_l){ + if (dstport_l){ + if (buf[UDP_DST_PORT_L_P]!=dstport_l){ + return(0); + } + } + if (buf[UDP_LEN_H_P]!=0 || buf[UDP_LEN_L_P]!=56 || buf[UDP_SRC_PORT_L_P]!=0x7b){ + // not ntp + return(0); + } + // copy time from the transmit time stamp field: + *time=((uint32_t)buf[0x52]<<24)|((uint32_t)buf[0x53]<<16)|((uint32_t)buf[0x54]<<8)|((uint32_t)buf[0x55]); + return(1); +} +#endif + +#ifdef UDP_client +// -------------------- send a spontanious UDP packet to a server +// There are two ways of using this: +// 1) you call send_udp_prepare, you fill the data yourself into buf starting at buf[UDP_DATA_P], +// you send the packet by calling send_udp_transmit +// +// 2) You just allocate a large enough buffer for you data and you call send_udp and nothing else +// needs to be done. +// +void send_udp_prepare(uint8_t *buf,uint16_t sport, const uint8_t *dip, uint16_t dport,const uint8_t *dstmac) +{ + uint8_t i=0; + // + while(i<6){ + buf[ETH_DST_MAC +i]=dstmac[i]; // gw mac in local lan or host mac + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } + buf[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; + buf[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; + fill_buf_p(&buf[IP_P],9,iphdr); + buf[IP_ID_L_P]=ipid; ipid++; + // total length field in the IP header must be set: + buf[IP_TOTLEN_H_P]=0; + // done in transmit: buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+UDP_HEADER_LEN+datalen; + buf[IP_PROTO_P]=IP_PROTO_UDP_V; + i=0; + while(i<4){ + buf[IP_DST_P+i]=dip[i]; + buf[IP_SRC_P+i]=ipaddr[i]; + i++; + } + // done in transmit: fill_ip_hdr_checksum(buf); + buf[UDP_DST_PORT_H_P]=(dport>>8); + buf[UDP_DST_PORT_L_P]=0xff&dport; + buf[UDP_SRC_PORT_H_P]=(sport>>8); + buf[UDP_SRC_PORT_L_P]=sport&0xff; + buf[UDP_LEN_H_P]=0; + // done in transmit: buf[UDP_LEN_L_P]=UDP_HEADER_LEN+datalen; + // zero the checksum + buf[UDP_CHECKSUM_H_P]=0; + buf[UDP_CHECKSUM_L_P]=0; + // copy the data: + // now starting with the first byte at buf[UDP_DATA_P] + // +} + +void send_udp_transmit(uint8_t *buf,uint16_t datalen) +{ + uint16_t tmp16; + tmp16=IP_HEADER_LEN+UDP_HEADER_LEN+datalen; + buf[IP_TOTLEN_L_P]=tmp16& 0xff; + buf[IP_TOTLEN_H_P]=tmp16>>8; + fill_ip_hdr_checksum(buf); + tmp16=UDP_HEADER_LEN+datalen; + buf[UDP_LEN_L_P]=tmp16& 0xff; + buf[UDP_LEN_H_P]=tmp16>>8; + // + tmp16=checksum(&buf[IP_SRC_P], 16 + datalen,1); + buf[UDP_CHECKSUM_L_P]=tmp16& 0xff; + buf[UDP_CHECKSUM_H_P]=tmp16>>8; + enc28j60PacketSend(UDP_HEADER_LEN+IP_HEADER_LEN+ETH_HEADER_LEN+datalen,buf); +} + +void send_udp(uint8_t *buf,char *data,uint8_t datalen,uint16_t sport, const uint8_t *dip, uint16_t dport,const uint8_t *dstmac) +{ + send_udp_prepare(buf,sport, dip, dport,dstmac); + uint8_t i=0; + // limit the length: + if (datalen>220){ + datalen=220; + } + // copy the data: + i=0; + while(i>8; + buf[UDP_CHECKSUM_L_P]=ck& 0xff; + enc28j60PacketSend(pos,buf); +} +#endif // WOL_client + +#if defined GRATARP +// Send a Gratuitous arp, this is to refresh the arp +// cash of routers and switches. It can improve the response +// time in wifi networks as some wifi equipment expects the initial +// communication to not start from the network side. That is wrong +// but some consumer devices are made like this. +// +// A Gratuitous ARP can be a request or a reply. +// A request frame is as well called Unsolicited ARP +uint8_t gratutious_arp(uint8_t *buf) +{ + uint8_t i=0; + if (!enc28j60linkup()){ + return(0); + } + // + while(i<6){ + buf[ETH_DST_MAC +i]=0xff; + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } + buf[ETH_TYPE_H_P] = ETHTYPE_ARP_H_V; + buf[ETH_TYPE_L_P] = ETHTYPE_ARP_L_V; + // arp request and reply are the same execept for + // the opcode: + fill_buf_p(&buf[ETH_ARP_P],8,arpreqhdr); + //buf[ETH_ARP_OPCODE_L_P]=ETH_ARP_OPCODE_REPLY_L_V; // reply + i=0; + while(i<6){ + buf[ETH_ARP_SRC_MAC_P +i]=macaddr[i]; + buf[ETH_ARP_DST_MAC_P+i]=0xff; + i++; + } + i=0; + while(i<4){ + buf[ETH_ARP_DST_IP_P+i]=ipaddr[i]; + buf[ETH_ARP_SRC_IP_P+i]=ipaddr[i]; + i++; + } + // 0x2a=42=len of packet + enc28j60PacketSend(0x2a,buf); + return(1); +} +#endif // GRATARP + +#if ARP_MAC_resolver_client +// make a arp request +// Note: you must have initialized the stack with +// init_udp_or_www_server or client_ifconfig +// before you can use this function +void client_arp_whohas(uint8_t *buf,uint8_t *ip_we_search) +{ + uint8_t i=0; + if (ipaddr[0]==0) return; // error ipaddr not set + // + while(i<6){ + buf[ETH_DST_MAC +i]=0xff; + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } + buf[ETH_TYPE_H_P] = ETHTYPE_ARP_H_V; + buf[ETH_TYPE_L_P] = ETHTYPE_ARP_L_V; + fill_buf_p(&buf[ETH_ARP_P],8,arpreqhdr); + i=0; + while(i<6){ + buf[ETH_ARP_SRC_MAC_P +i]=macaddr[i]; + buf[ETH_ARP_DST_MAC_P+i]=0; + i++; + } + i=0; + while(i<4){ + buf[ETH_ARP_DST_IP_P+i]=*(ip_we_search +i); + buf[ETH_ARP_SRC_IP_P+i]=ipaddr[i]; + i++; + } + // 0x2a=42=len of packet + enc28j60PacketSend(0x2a,buf); +} + +// return zero when current transaction is finished +uint8_t get_mac_with_arp_wait(void) +{ + if (arpip_state == WGW_HAVE_MAC){ + return(0); + } + return(1); +} + +// reference_number is something that is just returned in the callback +// to make matching and waiting for a given ip/mac address pair easier +// Note: you must have initialized the stack with +// init_udp_or_www_server or client_ifconfig +// before you can use this function +void get_mac_with_arp(uint8_t *ip, uint8_t reference_number,void (*arp_result_callback)(uint8_t *ip,uint8_t reference_number,uint8_t *mac)) +{ + uint8_t i=0; + client_arp_result_callback=arp_result_callback; + arpip_state=WGW_INITIAL_ARP; // causes an arp request in the packet loop + arp_reference_number=reference_number; + while(i<4){ + arpip[i]=ip[i]; + i++; + } +} +#endif + +#if defined (TCP_client) +// Make a tcp syn packet +void tcp_client_syn(uint8_t *buf,uint8_t srcport,uint16_t dstport) +{ + uint16_t ck; + uint8_t i=0; + // -- make the main part of the eth/IP/tcp header: + while(i<6){ + buf[ETH_DST_MAC +i]=tcp_dst_mac[i]; // gw mac in local lan or host mac + buf[ETH_SRC_MAC +i]=macaddr[i]; + i++; + } + buf[ETH_TYPE_H_P] = ETHTYPE_IP_H_V; + buf[ETH_TYPE_L_P] = ETHTYPE_IP_L_V; + fill_buf_p(&buf[IP_P],9,iphdr); + buf[IP_TOTLEN_L_P]=44; // good for syn + buf[IP_ID_L_P]=ipid; ipid++; + buf[IP_PROTO_P]=IP_PROTO_TCP_V; + i=0; + while(i<4){ + buf[IP_DST_P+i]=tcp_otherside_ip[i]; + buf[IP_SRC_P+i]=ipaddr[i]; + i++; + } + fill_ip_hdr_checksum(buf); + buf[TCP_DST_PORT_H_P]=(dstport>>8)&0xff; + buf[TCP_DST_PORT_L_P]=(dstport&0xff); + buf[TCP_SRC_PORT_H_P]=TCPCLIENT_SRC_PORT_H; + buf[TCP_SRC_PORT_L_P]=srcport; // lower 8 bit of src port + i=0; + // zero out sequence number and acknowledgement number + while(i<8){ + buf[TCP_SEQ_H_P+i]=0; + i++; + } + // -- header ready + // put inital seq number + // we step only the second byte, this allows us to send packts + // with 255 bytes 512 (if we step the initial seqnum by 2) + // or 765 (step by 3) + buf[TCP_SEQ_H_P+2]= seqnum; + // step the inititial seq num by something we will not use + // during this tcp session: + seqnum+=3; + buf[TCP_HEADER_LEN_P]=0x60; // 0x60=24 len: (0x60>>4) * 4 + buf[TCP_FLAGS_P]=TCP_FLAGS_SYN_V; + // use a low window size otherwise we have to have + // timers and can not just react on every packet. + buf[TCP_WIN_SIZE]=0x3; // 1024=0x400 768=0x300, initial window + buf[TCP_WIN_SIZE+1]=0x0; + // zero the checksum + buf[TCP_CHECKSUM_H_P]=0; + buf[TCP_CHECKSUM_L_P]=0; + // urgent pointer + buf[TCP_CHECKSUM_L_P+1]=0; + buf[TCP_CHECKSUM_L_P+2]=0; + // MSS= max IP len that we want to have: + buf[TCP_OPTIONS_P]=2; + buf[TCP_OPTIONS_P+1]=4; + buf[TCP_OPTIONS_P+2]=(CLIENTMSS>>8); + buf[TCP_OPTIONS_P+3]=CLIENTMSS & 0xff; + ck=checksum(&buf[IP_SRC_P], 8 +TCP_HEADER_LEN_PLAIN+4,2); + buf[TCP_CHECKSUM_H_P]=ck>>8; + buf[TCP_CHECKSUM_L_P]=ck& 0xff; + // 4 is the tcp mss option: + enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+ETH_HEADER_LEN+4,buf); +} +#endif // TCP_client + +#if defined (TCP_client) +// This is how to use the tcp client: +// +// Declare a callback function to get the result (tcp data from the server): +// +// uint8_t your_client_tcp_result_callback(uint8_t fd, uint8_t statuscode,uint16_t data_start_pos_in_buf, uint16_t len_of_data){...your code;return(close_tcp_session);} +// +// statuscode=0 means the buffer has valid data, otherwise len and pos_in_buf +// are invalid. That is: do to use data_start_pos_in_buf and len_of_data +// if statuscode!=0. +// +// This callback gives you access to the TCP data of the first +// packet returned from the server. You should aim to minimize the server +// output such that this will be the only packet. +// +// close_tcp_session=1 means close the session now. close_tcp_session=0 +// read all data and leave it to the other side to close it. +// If you connect to a web server then you want close_tcp_session=0. +// If you connect to a modbus/tcp equipment then you want close_tcp_session=1 +// +// Declare a callback function to be called in order to fill in the +// +// request (tcp data sent to the server): +// uint16_t your_client_tcp_datafill_callback(uint8_t fd){...your code;return(len_of_data_filled_in);} +// +// Now call: +// fd=client_tcp_req(&your_client_tcp_result_callback,&your_client_tcp_datafill_callback,portnumber); +// +// fd is a file descriptor like number that you get back in the fill and result +// function so you know to which call of client_tcp_req this callback belongs. +// +// You can not start different clients (e.g modbus and web) at the +// same time but you can start them one after each other. That is +// when the request has timed out or when the result_callback was +// executed then you can start a new one. The fd makes it still possible to +// distinguish in the callback code the different types you started. +// +// Note that you might never get called back if the other side does +// not answer. A timer would be needed to recongnize such a condition. +// +// We use callback functions because that saves memory and a uC is very +// limited in memory +// +uint8_t client_tcp_req(uint8_t (*result_callback)(uint8_t fd,uint8_t statuscode,uint16_t data_start_pos_in_buf, uint16_t len_of_data),uint16_t (*datafill_callback)(uint8_t fd),uint16_t port,uint8_t *dstip,uint8_t *dstmac) +{ + uint8_t i=0; + client_tcp_result_callback=result_callback; + client_tcp_datafill_callback=datafill_callback; + while(i<4){tcp_otherside_ip[i]=dstip[i];i++;} + i=0; + while(i<6){tcp_dst_mac[i]=dstmac[i];i++;} + tcp_client_port=port; + tcp_client_state=1; // send a syn + tcp_fd++; + if (tcp_fd>7){ + tcp_fd=0; + } + return(tcp_fd); +} +#endif // TCP_client + +#if defined (WWW_client) +uint16_t www_client_internal_datafill_callback(uint8_t fd){ + char strbuf[5]; + uint16_t len=0; + if (fd==www_fd){ + if (browsertype==0){ + // GET + len=fill_tcp_data_p(bufptr,0,PSTR("GET ")); + len=fill_tcp_data_p(bufptr,len,client_urlbuf_p); + len=fill_tcp_data(bufptr,len,client_urlbuf_var); + // I would prefer http/1.0 but there is a funny + // bug in some apache webservers which causes + // them to send two packets (fragmented PDU) + // if we don't use HTTP/1.1 + Connection: close + len=fill_tcp_data_p(bufptr,len,PSTR(" HTTP/1.1\r\nHost: ")); + len=fill_tcp_data_p(bufptr,len,client_hoststr); + len=fill_tcp_data_p(bufptr,len,PSTR("\r\nUser-Agent: tgr/1.1\r\nAccept: text/html\r\n\r\n")); + }else{ + // POST + len=fill_tcp_data_p(bufptr,0,PSTR("POST ")); + len=fill_tcp_data_p(bufptr,len,client_urlbuf_p); + len=fill_tcp_data(bufptr,len,client_urlbuf_var); + len=fill_tcp_data_p(bufptr,len,PSTR(" HTTP/1.1\r\nHost: ")); + len=fill_tcp_data_p(bufptr,len,client_hoststr); + if (client_additionalheaderline_p){ + len=fill_tcp_data_p(bufptr,len,PSTR("\r\n")); + len=fill_tcp_data_p(bufptr,len,client_additionalheaderline_p); + } + len=fill_tcp_data_p(bufptr,len,PSTR("\r\nUser-Agent: tgr/1.1\r\nAccept: */*\r\n")); + len=fill_tcp_data_p(bufptr,len,PSTR("Content-Length: ")); + itoa(strlen(client_postval),strbuf,10); + len=fill_tcp_data(bufptr,len,strbuf); + len=fill_tcp_data_p(bufptr,len,PSTR("\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n")); + len=fill_tcp_data(bufptr,len,client_postval); + } + return(len); + } + return(0); +} + +uint8_t www_client_internal_result_callback(uint8_t fd, uint8_t statuscode, uint16_t datapos, uint16_t len_of_data){ + uint16_t web_statuscode=0; // tcp status is OK but we need to check http layer too + uint8_t i=0; + if (fd!=www_fd){ + (*client_browser_callback)(500,0,0); + return(0); + } + if (statuscode==0 && len_of_data>12){ + // we might have a http status code + // http status codes are 3digit numbers as ascii text. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + // The buffer would look like this: HTTP/1.1 200 OK\r\n + // web_statuscode=0 means we got a corrupted answer + if (client_browser_callback){ + if (isblank(bufptr[datapos+8]) && isdigit(bufptr[datapos+9])&& isdigit(bufptr[datapos+11])){ // e.g 200 OK, a status code has 3 digits from datapos+9 to datapos+11, copy over the web/http status code to web_statuscode: + while(i<2){ + web_statuscode+=bufptr[datapos+9+i]-'0'; + web_statuscode*=10; + i++; + } + web_statuscode+=bufptr[datapos+11]-'0'; + } + //(*client_browser_callback)(web_statuscode,((uint16_t)TCP_SRC_PORT_H_P+(bufptr[TCP_HEADER_LEN_P]>>4)*4),len_of_data); + (*client_browser_callback)(web_statuscode,datapos,len_of_data); + } + } + return(0); +} + +// call this function externally like this: +// +// Declare a callback function: void browserresult(uint8_t webstatuscode,uint16_t datapos,uint16_t len){...your code} +// The variable datapos is the index in the packet buffer. +// Now call client_browser_url: +// client_browser_url(PSTR("/cgi-bin/checkip"),NULL,"tuxgraphics.org",&browserresult,other_side_ip,gwmac); +// urlbuf_varpart is a pointer to a string buffer that contains the second +// non constant part of the url. You must keep this buffer allocated until the +// callback function is executed or until you can be sure that the server side +// has timed out. +// hoststr is the name of the host. This is needed because many sites host several +// sites on the same physical machine with only one IP address. The web server needs +// to know to which site you want to go. +// webstatuscode is zero if there was no proper reply from the server (garbage message total communication failure, this is rare). +// webstatuscode is the http status code (e.g webstatuscode=200 for 200 OK); +// webstatuscode is zero if there was a garbage answer received from the server. +// For possible status codes look at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +// Basically 2xx is success and any 5xx, 4xx is a failure. +// The string buffers to which urlbuf_varpart and hoststr are pointing +// must not be changed until the callback is executed. +// +void client_browse_url(const char *urlbuf_p,char *urlbuf_varpart,const char *hoststr,void (*callback)(uint16_t,uint16_t,uint16_t),uint8_t *dstip,uint8_t *dstmac) +{ + if (!enc28j60linkup())return; + client_urlbuf_p=urlbuf_p; + client_urlbuf_var=urlbuf_varpart; + client_hoststr=hoststr; + browsertype=0; + client_browser_callback=callback; + www_fd=client_tcp_req(&www_client_internal_result_callback,&www_client_internal_datafill_callback,80,dstip,dstmac); +} + +// client web browser using http POST operation: +// additionalheaderline_p must be set to NULL if not used. +// The string buffers to which urlbuf_varpart and hoststr are pointing +// must not be changed until the callback is executed. +// postval is a string buffer which can only be de-allocated by the caller +// when the post operation was really done (e.g when callback was executed). +// postval must be urlencoded. +void client_http_post(const char *urlbuf_p, char *urlbuf_varpart,const char *hoststr, const char *additionalheaderline_p,char *postval,void (*callback)(uint16_t,uint16_t,uint16_t),uint8_t *dstip,uint8_t *dstmac) +{ + if (!enc28j60linkup())return; + client_urlbuf_p=urlbuf_p; + client_hoststr=hoststr; + client_urlbuf_var=urlbuf_varpart; + client_additionalheaderline_p=additionalheaderline_p; + client_postval=postval; + browsertype=1; + client_browser_callback=callback; + www_fd=client_tcp_req(&www_client_internal_result_callback,&www_client_internal_datafill_callback,80,dstip,dstmac); +} +#endif // WWW_client + +void register_ping_rec_callback(void (*callback)(uint8_t *srcip)) +{ + icmp_callback=callback; +} + +#ifdef PING_client +// loop over this to check if we get a ping reply: +uint8_t packetloop_icmp_checkreply(uint8_t *buf,uint8_t *ip_monitoredhost) +{ + if(buf[IP_PROTO_P]==IP_PROTO_ICMP_V && buf[ICMP_TYPE_P]==ICMP_TYPE_ECHOREPLY_V){ + if (buf[ICMP_DATA_P]== PINGPATTERN){ + if (check_ip_message_is_from(buf,ip_monitoredhost)){ + return(1); + // ping reply is from monitored host and ping was from us + } + } + } + return(0); +} +#endif // PING_client + + +// return 0 to just continue in the packet loop and return the position +// of the tcp data if there is tcp data part +uint16_t packetloop_arp_icmp_tcp(uint8_t *buf,uint16_t plen) +{ + uint16_t len; +#if defined (TCP_client) + uint8_t send_fin=0; + uint16_t tcpstart; + uint16_t save_len; +#endif +#ifdef ARP_MAC_resolver_client + //plen will be unequal to zero if there is a valid + // packet (without crc error): + if(plen==0){ + if (arpip_state == (WGW_ACCEPT_ARP_REPLY|WGW_INITIAL_ARP) && arp_delaycnt==0 ){ + // arp_delaycnt has wrapped no arp reply yet + if (enc28j60linkup()) client_arp_whohas(buf,arpip); + } + if (arpip_state == WGW_INITIAL_ARP && enc28j60linkup()){ + client_arp_whohas(buf,arpip); + arpip_state|=WGW_ACCEPT_ARP_REPLY; // WGW_INITIAL_ARP and WGW_ACCEPT_ARP_REPLY set + arp_delaycnt=0; // this is like a timer, not so precise but good enough, it wraps in about 2 sec + } + arp_delaycnt++; +#if defined (TCP_client) + if (tcp_client_state==1 && enc28j60linkup()){ // send a syn + tcp_client_state=2; + tcpclient_src_port_l++; // allocate a new port + // we encode our 3 bit fd into the src port this + // way we get it back in every message that comes + // from the server: + tcp_client_syn(buf,((tcp_fd<<5) | (0x1f & tcpclient_src_port_l)),tcp_client_port); + } +#endif + return(0); + } +#endif // ARP_MAC_resolver_client + // arp is broadcast if unknown but a host may also + // verify the mac address by sending it to + // a unicast address. + if(eth_type_is_arp_and_my_ip(buf,plen)){ + if (buf[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REQ_L_V){ + // is it an arp request + make_arp_answer_from_request(buf); + } +#ifdef ARP_MAC_resolver_client + if ((arpip_state & WGW_ACCEPT_ARP_REPLY) && (buf[ETH_ARP_OPCODE_L_P]==ETH_ARP_OPCODE_REPLY_L_V)){ + // is it an arp reply + if (memcmp(&buf[ETH_ARP_SRC_IP_P],arpip,4)!=0) return(0); // not an arp reply for the IP we were searching + (*client_arp_result_callback)(arpip,arp_reference_number,buf+ETH_ARP_SRC_MAC_P); + arpip_state=WGW_HAVE_MAC; + } +#endif // ARP_MAC_resolver_client + return(0); + + } + // check if ip packets are for us: + if(eth_type_is_ip_and_my_ip(buf,plen)==0){ + return(0); + } + if(buf[IP_PROTO_P]==IP_PROTO_ICMP_V && buf[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V){ + if (icmp_callback){ + (*icmp_callback)(&(buf[IP_SRC_P])); + } + // a ping packet, let's send pong + debugstr("Replying to ping..."); debugcrlf(); + make_echo_reply_from_request(buf,plen); + return(0); + } + // this is an important check to avoid working on the wrong packets: + if (plen<54 || buf[IP_PROTO_P]!=IP_PROTO_TCP_V ){ + // smaller than the smallest TCP packet (TCP packet with no options section) or not tcp port + return(0); + } +#if defined (TCP_client) + // a message for the tcp client, tcp_client_state is zero if client was never used + if ( buf[TCP_DST_PORT_H_P]==TCPCLIENT_SRC_PORT_H){ +#if defined (WWW_client) + // workaround to pass pointer to www_client_internal.. + bufptr=buf; +#endif // WWW_client + if (check_ip_message_is_from(buf,tcp_otherside_ip)==0){ + return(0); + } + // if we get a reset: + if (buf[TCP_FLAGS_P] & TCP_FLAGS_RST_V){ + if (client_tcp_result_callback){ + // parameters in client_tcp_result_callback: fd, status, buf_start, len + (*client_tcp_result_callback)((buf[TCP_DST_PORT_L_P]>>5)&0x7,3,0,0); + } + tcp_client_state=6; + return(0); + } + len=get_tcp_data_len(buf); + if (tcp_client_state==2){ + if ((buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V) && (buf[TCP_FLAGS_P] &TCP_FLAGS_ACK_V)){ + // synack, answer with ack + make_tcp_ack_from_any(buf,0,0); + buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V|TCP_FLAGS_PUSH_V; + + // Make a tcp message with data. When calling this function we must + // still have a valid tcp-ack in the buffer. In other words + // you have just called make_tcp_ack_from_any(buf,0). + if (client_tcp_datafill_callback){ + // in this case it is src port because the above + // make_tcp_ack_from_any swaps the dst and src port: + len=(*client_tcp_datafill_callback)((buf[TCP_SRC_PORT_L_P]>>5)&0x7); + }else{ + // this is just to prevent a crash + len=0; + } + tcp_client_state=3; + make_tcp_ack_with_data_noflags(buf,len); + return(0); + }else{ + // reset only if we have sent a syn and don't get syn-ack back. + // If we connect to a non listen port then we get a RST + // which will be handeled above. In other words there is + // normally no danger for an endless loop. + tcp_client_state=1; // retry + // do not inform application layer as we retry. + len++; + if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V){ + // if packet was an ack then do not step the ack number + len=0; + } + // refuse and reset the connection + make_tcp_ack_from_any(buf,len,TCP_FLAGS_RST_V); + return(0); + } + } + // in tcp_client_state==3 we will normally first get an empty + // ack-packet and then a ack-packet with data. + if (tcp_client_state==3 && len>0){ + // our first real data packet + tcp_client_state=4; + // return the data we received + if (client_tcp_result_callback){ + tcpstart=TCP_DATA_START; // TCP_DATA_START is a formula + // out of buffer bounds check, needed in case of fragmented IP packets + if (tcpstart>plen-8){ + tcpstart=plen-8; // dummy but save + } + save_len=len; + if (tcpstart+len>plen){ + save_len=plen-tcpstart; + } + send_fin=(*client_tcp_result_callback)((buf[TCP_DST_PORT_L_P]>>5)&0x7,0,tcpstart,save_len); + } + if (send_fin){ + make_tcp_ack_from_any(buf,len,TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V); + tcp_client_state=5; + return(0); + } + } + if(tcp_client_state==5){ + // we get one more final ack to our fin-ack: + if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V){ + tcp_client_state=6; // in state 6 communication should be finished + } + return(0); + } + if(tcp_client_state==6){ + // something wrong, can't deal with this, reset the connection + len++; + if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V) len=0; // if packet was an ack then do not step the ack number + make_tcp_ack_from_any(buf,len,TCP_FLAGS_RST_V); + // just a single reset, do not repeat if more messages: + tcp_client_state=7; + return(0); + } + if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V){ + // this normally a fin ack message but it could be + // any message with fin we answer with fin-ack: + make_tcp_ack_from_any(buf,len+1,TCP_FLAGS_FIN_V); + tcp_client_state=5; // connection terminated + return(0); + } + // ack all data (the web page may be long): + // if we just get a fragment then len will be zero + // and we ack only once we have the full packet + if (len>0){ + make_tcp_ack_from_any(buf,len,0); + } + return(0); + } +#endif // TCP_client + // +#ifdef WWW_server + // tcp port web server start + if (buf[TCP_DST_PORT_H_P]==wwwport_h && buf[TCP_DST_PORT_L_P]==wwwport_l){ + if (buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V){ + make_tcp_synack_from_syn(buf); + // make_tcp_synack_from_syn does already send the syn,ack + return(0); + } + if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V){ + info_data_len=get_tcp_data_len(buf); + // we can possibly have no data, just ack: + // Here we misuse plen for something else to save a variable. + // plen is now the position of start of the tcp user data. + if (info_data_len==0){ + if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V){ + // finack, answer with ack + make_tcp_ack_from_any(buf,0,0); + } + // just an ack with no data, wait for next packet + return(0); + } + // Here we misuse len for something else to save a variable + len=TCP_DATA_START; // TCP_DATA_START is a formula + // check for data corruption + if (len>plen-8){ + return(0); + } + return(len); + } + } +#endif // WWW_server + return(0); +} +/* end of ip_arp_udp.c */ diff --git a/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_arp_udp_tcp.h b/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_arp_udp_tcp.h new file mode 100644 index 0000000..e4b2e57 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_arp_udp_tcp.h @@ -0,0 +1,207 @@ +/********************************************* + * vim:sw=8:ts=8:si:et + * To use the above modeline in vim you must have "set modeline" in your .vimrc + * Author: Guido Socher + * Copyright:LGPL V2 + * See http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html + * + * IP/ARP/UDP/TCP functions + * + *********************************************/ +//@{ +#ifndef IP_ARP_UDP_TCP_H +#define IP_ARP_UDP_TCP_H 1 + +#include "ip_config.h" + +// set my own mac address: +extern void init_mac(uint8_t *mymac); // not needed if you call init_udp_or_www_server +// -- web server functions -- +#if defined (WWW_server) || defined (UDP_server) +// you must call this function once before you use any of the other server functions: +// mymac may be set to NULL in this function if init_mac was used before +// init_ip_arp_udp_tcp is now replaced by init_udp_or_www_server and the www_server_port function. +extern void init_udp_or_www_server(uint8_t *mymac,uint8_t *myip); +#endif + +#if defined (WWW_server) +extern void www_server_port(uint16_t port); // not needed if you want port 80 +// send data from the web server to the client: +extern void www_server_reply(uint8_t *buf,uint16_t dlen); +#endif + +// for a UDP server: +#if defined (UDP_server) +extern uint8_t eth_type_is_ip_and_my_ip(uint8_t *buf,uint16_t len); +extern void make_udp_reply_from_request_udpdat_ready(uint8_t *buf,uint16_t datalen,uint16_t port); +extern void make_udp_reply_from_request(uint8_t *buf,char *data,uint8_t datalen,uint16_t port); +#endif +// return 0 to just continue in the packet loop and return the position +// of the tcp data if there is tcp data part: +extern uint16_t packetloop_arp_icmp_tcp(uint8_t *buf,uint16_t plen); +// functions to fill the web pages with data: +extern uint16_t fill_tcp_data_p(uint8_t *buf,uint16_t pos, const uint8_t *progmem_s); +extern uint16_t fill_tcp_data_string(uint8_t *buf,uint16_t pos, char *reply); + +extern uint16_t fill_tcp_data(uint8_t *buf,uint16_t pos, const char *s); +// fill a binary string of len data into the tcp packet: +extern uint16_t fill_tcp_data_len(uint8_t *buf,uint16_t pos, const uint8_t *s, uint8_t len); + +// -- client only functions -- +#if defined (WWW_client) || defined (NTP_client) || defined (UDP_client) || defined (TCP_client) || defined (PING_client) +extern void client_ifconfig(uint8_t *ip,uint8_t *netmask); +// route_via_gw can be used decide if a packed needs to be routed via GW or can be found on the LAN: +extern uint8_t route_via_gw(uint8_t *destip); // returns 1 if destip must be routed via the GW. Returns 0 if destip is on the local LAN +// +// The get_mac_with_arp function can be used to find the MAC address of +// a host that is directly connected to the same LAN. It translates the IP address into +// a MAC address. +// You need to provide a callback function. That function will be executed once the +// MAC address is found. We do this to not block execution in the main loop. +// NOTE: you can only do one MAC address resolution at a time. The reference_number is just +// a number given back to you to make it easier to know what this relates to. +// +// You declare the callback function: +// +//#define TRANS_NUM_GWMAC 12 +//void arpresolver_result_callback(uint8_t *ip __attribute__((unused)),uint8_t reference_number,uint8_t *mac){ // the __attribute__((unused)) is a gcc compiler directive to avoid warnings about unsed variables. +// uint8_t i=0; +// if (reference_number==TRANS_NUM_GWMAC){ +// // copy mac address over: +// while(i<6){gwmac[i]=mac[i];i++;} +// } +//} +// +// and then you can just call get_mac_with_arp like this: +// get_mac_with_arp(gwip,TRANS_NUM_GWMAC,&arpresolver_result_callback); +// Note: you must have initialized the stack with init_udp_or_www_server or client_ifconfig +// before you can use get_mac_with_arp(). The arp request will automatically be repeated if +// there is no answer. +extern void get_mac_with_arp(uint8_t *ip, uint8_t reference_number,void (*arp_result_callback)(uint8_t *ip,uint8_t reference_number,uint8_t *mac)); +uint8_t get_mac_with_arp_wait(void); // checks current ongoing transaction, retuns 0 when the transaction is over +#endif + +#ifdef TCP_client +// To use the tcp client you need to: +// +// Declare a callback function to get the result (tcp data from the server): +// +// uint8_t your_client_tcp_result_callback(uint8_t fd, uint8_t statuscode,uint16_t data_start_pos_in_buf, uint16_t len_of_data){ +// ...your code; +// return(close_tcp_session); +// } +// +// statuscode=0 means the buffer has valid data, otherwise len and pos_in_buf +// are invalid. That is: do to use data_start_pos_in_buf and len_of_data +// if statuscode!=0. +// +// This callback gives you access to the TCP data of the first +// packet returned from the server. You should aim to minimize the server +// output such that this will be the only packet. +// +// close_tcp_session=1 means close the session now. close_tcp_session=0 +// read all data and leave it to the other side to close it. +// If you connect to a web server then you want close_tcp_session=0. +// If you connect to a modbus/tcp equipment then you want close_tcp_session=1 +// +// Declare a callback function to be called in order to fill in the +// +// request (tcp data sent to the server): +// uint16_t your_client_tcp_datafill_callback(uint8_t fd){...your code;return(len_of_data_filled_in);} +// +// Now call: +// fd=client_tcp_req(&your_client_tcp_result_callback,&your_client_tcp_datafill_callback,portnumber); +// +// fd is a file descriptor like number that you get back in the fill and result +// function so you know to which call of client_tcp_req this callback belongs. +// +// You can not start different clients (e.g modbus and web) at the +// same time but you can start them one after each other. That is +// when the request has timed out or when the result_callback was +// executed then you can start a new one. The fd makes it still possible to +// distinguish in the callback code the different types you started. +// +// Note that you might never get called back if the other side does +// not answer. A timer would be needed to recongnize such a condition. +// +// We use callback functions because that is the best implementation +// given the fact that we have very little RAM memory. +// +extern uint8_t client_tcp_req(uint8_t (*result_callback)(uint8_t fd,uint8_t statuscode,uint16_t data_start_pos_in_buf, uint16_t len_of_data),uint16_t (*datafill_callback)(uint8_t fd),uint16_t port,uint8_t *dstip,uint8_t *dstmac); +#endif + +#ifdef WWW_client +// ----- http get +// The string buffers to which urlbuf_varpart and hoststr are pointing +// must not be changed until the callback is executed. +extern void client_browse_url(const prog_char *urlbuf, char *urlbuf_varpart, const char *hoststr,void (*callback)(uint16_t,uint16_t,uint16_t),uint8_t *dstip,uint8_t *dstmac); +// The callback is a reference to a function which must look like this: +// void browserresult_callback(uint16_t webstatuscode,uint16_t datapos,uint16_t len) +// webstatuscode is zero if there was no proper reply from the server (garbage message total communication failure, this is rare). +// webstatuscode is otherwise the http status code (e.g webstatuscode=200 for 200 OK); +// For possible status codes look at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +// Basically 2xx is success and any 5xx, 4xx is a failure. +// ----- http post +// client web browser using http POST operation: +// additionalheaderline must be set to NULL if not used. +// The string buffers to which urlbuf_varpart and hoststr are pointing +// must not be changed until the callback is executed. +// postval is a string buffer which can only be de-allocated by the caller +// when the post operation was really done (e.g when callback was executed). +// postval must be urlencoded. +extern void client_http_post(const prog_char *urlbuf, char *urlbuf_varpart,const char *hoststr, const prog_char *additionalheaderline,char *postval,void (*callback)(uint16_t,uint16_t,uint16_t),uint8_t *dstip,uint8_t *dstmac); +// The callback is a reference to a function which must look like this: +// void browserresult_callback(uint16_t webstatuscode,uint16_t datapos,uint16_t len) +// webstatuscode is zero if there was no proper reply from the server (garbage message total communication failure, this is rare). +// webstatuscode is otherwise the http status code (e.g webstatuscode=200 for 200 OK); +#endif + +#ifdef NTP_client +// be careful to not mix client_ntp_request with situations where you are filling +// a web-page. Normally you will be using the same packet buffer and +// client_ntp_request writes immediately to buf. You might need to +// set a marker and call client_ntp_request when your main loop is idle. +extern void client_ntp_request(uint8_t *buf,uint8_t *ntpip,uint8_t srcport,uint8_t *dstmac); +extern uint8_t client_ntp_process_answer(uint8_t *buf,uint32_t *time,uint8_t dstport_l); +#endif + +#ifdef UDP_client +// There are two ways of using this UDP client: +// +// 1) you call send_udp_prepare, you fill the data yourself into buf starting at buf[UDP_DATA_P], +// you send the packet by calling send_udp_transmit +// +// 2) You just allocate a large enough buffer for you data and you call send_udp and nothing else +// needs to be done. +// +extern void send_udp_prepare(uint8_t *buf,uint16_t sport, const uint8_t *dip, uint16_t dport,const uint8_t *dstmac); +extern void send_udp_transmit(uint8_t *buf,uint16_t datalen); + +// send_udp sends via gwip, you must call client_set_gwip at startup, datalen must be less than 220 bytes +extern void send_udp(uint8_t *buf,char *data,uint8_t datalen,uint16_t sport, const uint8_t *dip, uint16_t dport,const uint8_t *dstmac); +#endif + +// you can find out who ping-ed you if you want: +extern void register_ping_rec_callback(void (*callback)(uint8_t *srcip)); + +#ifdef PING_client +extern void client_icmp_request(uint8_t *buf,uint8_t *destip,uint8_t *dstmac); +// you must loop over this function to check if there was a ping reply: +extern uint8_t packetloop_icmp_checkreply(uint8_t *buf,uint8_t *ip_monitoredhost); +#endif // PING_client + +#ifdef WOL_client +extern void send_wol(uint8_t *buf,uint8_t *wolmac); +#endif // WOL_client + +#if defined GRATARP +// send a Gratuitous arp, this is to refresh the arp +// cash of routers and switches. It can improve the response +// time in wifi networks as some wifi equipment expects the initial +// communication to not start from the network side. That is wrong +// but some consumer devices are made like this. +extern uint8_t gratutious_arp(uint8_t *buf); +#endif // GRATARP + +#endif /* IP_ARP_UDP_TCP_H */ +//@} diff --git a/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_config.h b/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_config.h new file mode 100644 index 0000000..b043ee9 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/tcpip/ip_config.h @@ -0,0 +1,53 @@ +/********************************************* + * vim:sw=8:ts=8:si:et + * To use the above modeline in vim you must have "set modeline" in your .vimrc + * Author: Guido Socher + * Copyright:LGPL V2 + * See http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html + * + * This file can be used to decide which functionallity of the + * TCP/IP stack shall be available. By picking the right functions + * you can significantly reduce the size of the resulting code. + * + *********************************************/ +//@{ +#ifndef IP_CONFIG_H +#define IP_CONFIG_H + +//------------- functions in ip_arp_udp_tcp.c -------------- +// an NTP client (ntp clock): +#undef NTP_client +// a spontanious sending UDP client (needed as well for DNS and DHCP) +#undef UDP_client +// a server answering to UDP messages +#define UDP_server +// a web server +#define WWW_server + +// to send out a ping: +#undef PING_client +#define PINGPATTERN 0x42 + +// a UDP wake on lan sender: +#undef WOL_client + +// function to send a gratuitous arp +#undef GRATARP + +// a "web browser". This can be use to upload data +// to a web server on the internet by encoding the data +// into the url (like a Form action of type GET): +#undef WWW_client +// if you do not need a browser and just a server: +//#undef WWW_client +// +//------------- functions in websrv_help_functions.c -------------- +// +// functions to decode cgi-form data: +#undef FROMDECODE_websrv_help + +// function to encode a URL (mostly needed for a web client) +#undef URLENCODE_websrv_help + +#endif /* IP_CONFIG_H */ +//@} diff --git a/part15a-tcpip-webserver-gpio-interrupt/tcpip/net.h b/part15a-tcpip-webserver-gpio-interrupt/tcpip/net.h new file mode 100644 index 0000000..1e9aed2 --- /dev/null +++ b/part15a-tcpip-webserver-gpio-interrupt/tcpip/net.h @@ -0,0 +1,133 @@ +/********************************************* + * vim:sw=8:ts=8:si:et + * To use the above modeline in vim you must have "set modeline" in your .vimrc + * Author: Guido Socher + * Copyright:LGPL V2 + * See http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html + * + * Based on the net.h file from the AVRlib library by Pascal Stang. + * For AVRlib See http://www.procyonengineering.com/ + * Used with explicit permission of Pascal Stang. + * + *********************************************/ + +// notation: _P = position of a field +// _V = value of a field + +//@{ + +#ifndef NET_H +#define NET_H + +// ******* ETH ******* +#define ETH_HEADER_LEN 14 +// values of certain bytes: +#define ETHTYPE_ARP_H_V 0x08 +#define ETHTYPE_ARP_L_V 0x06 +#define ETHTYPE_IP_H_V 0x08 +#define ETHTYPE_IP_L_V 0x00 +// byte positions in the ethernet frame: +// +// Ethernet type field (2bytes): +#define ETH_TYPE_H_P 12 +#define ETH_TYPE_L_P 13 +// +#define ETH_DST_MAC 0 +#define ETH_SRC_MAC 6 + + +// ******* ARP ******* +#define ETH_ARP_OPCODE_REPLY_H_V 0x0 +#define ETH_ARP_OPCODE_REPLY_L_V 0x02 +#define ETH_ARP_OPCODE_REQ_H_V 0x0 +#define ETH_ARP_OPCODE_REQ_L_V 0x01 +// start of arp header: +#define ETH_ARP_P 0xe +// +#define ETHTYPE_ARP_L_V 0x06 +// arp.dst.ip +#define ETH_ARP_DST_IP_P 0x26 +// arp.opcode +#define ETH_ARP_OPCODE_H_P 0x14 +#define ETH_ARP_OPCODE_L_P 0x15 +// arp.src.mac +#define ETH_ARP_SRC_MAC_P 0x16 +#define ETH_ARP_SRC_IP_P 0x1c +#define ETH_ARP_DST_MAC_P 0x20 +#define ETH_ARP_DST_IP_P 0x26 + +// ******* IP ******* +#define IP_HEADER_LEN 20 +// ip.src +#define IP_SRC_P 0x1a +#define IP_DST_P 0x1e +#define IP_HEADER_LEN_VER_P 0xe +#define IP_CHECKSUM_P 0x18 +#define IP_TTL_P 0x16 +#define IP_FLAGS_P 0x14 +#define IP_P 0xe +#define IP_TOTLEN_H_P 0x10 +#define IP_TOTLEN_L_P 0x11 +#define IP_ID_H_P 0x12 +#define IP_ID_L_P 0x13 + +#define IP_PROTO_P 0x17 + +#define IP_PROTO_ICMP_V 1 +#define IP_PROTO_TCP_V 6 +// 17=0x11 +#define IP_PROTO_UDP_V 17 +// ******* ICMP ******* +#define ICMP_TYPE_ECHOREPLY_V 0 +#define ICMP_TYPE_ECHOREQUEST_V 8 +// +#define ICMP_TYPE_P 0x22 +#define ICMP_CHECKSUM_P 0x24 +#define ICMP_CHECKSUM_H_P 0x24 +#define ICMP_CHECKSUM_L_P 0x25 +#define ICMP_IDENT_H_P 0x26 +#define ICMP_IDENT_L_P 0x27 +#define ICMP_DATA_P 0x2a + +// ******* UDP ******* +#define UDP_HEADER_LEN 8 +// +#define UDP_SRC_PORT_H_P 0x22 +#define UDP_SRC_PORT_L_P 0x23 +#define UDP_DST_PORT_H_P 0x24 +#define UDP_DST_PORT_L_P 0x25 +// +#define UDP_LEN_H_P 0x26 +#define UDP_LEN_L_P 0x27 +#define UDP_CHECKSUM_H_P 0x28 +#define UDP_CHECKSUM_L_P 0x29 +#define UDP_DATA_P 0x2a + +// ******* TCP ******* +#define TCP_SRC_PORT_H_P 0x22 +#define TCP_SRC_PORT_L_P 0x23 +#define TCP_DST_PORT_H_P 0x24 +#define TCP_DST_PORT_L_P 0x25 +// the tcp seq number is 4 bytes 0x26-0x29 +#define TCP_SEQ_H_P 0x26 +#define TCP_SEQACK_H_P 0x2a +// flags: SYN=2 +#define TCP_FLAGS_P 0x2f +#define TCP_FLAGS_SYN_V 2 +#define TCP_FLAGS_FIN_V 1 +#define TCP_FLAGS_RST_V 4 +#define TCP_FLAGS_PUSH_V 8 +#define TCP_FLAGS_SYNACK_V 0x12 +#define TCP_FLAGS_ACK_V 0x10 +#define TCP_FLAGS_PSHACK_V 0x18 +// plain len without the options: +#define TCP_HEADER_LEN_PLAIN 20 +#define TCP_HEADER_LEN_P 0x2e +#define TCP_WIN_SIZE 0x30 +#define TCP_CHECKSUM_H_P 0x32 +#define TCP_CHECKSUM_L_P 0x33 +#define TCP_OPTIONS_P 0x36 +// +#endif +//@} + diff --git a/part2-building/Makefile b/part2-building/Makefile index 371b8b1..ae02b77 100644 --- a/part2-building/Makefile +++ b/part2-building/Makefile @@ -1,19 +1,19 @@ CFILES = $(wildcard *.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin all: clean kernel8.img boot.o: boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c boot.S -o boot.o + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot.o $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean: /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part2-building/Makefile.gcc b/part2-building/Makefile.gcc deleted file mode 100644 index ae02b77..0000000 --- a/part2-building/Makefile.gcc +++ /dev/null @@ -1,19 +0,0 @@ -CFILES = $(wildcard *.c) -OFILES = $(CFILES:.c=.o) -GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles -GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin - -all: clean kernel8.img - -boot.o: boot.S - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o - -%.o: %.c - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ - -kernel8.img: boot.o $(OFILES) - $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img - -clean: - /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part3-helloworld/Makefile b/part3-helloworld/Makefile index 371b8b1..db4db8f 100644 --- a/part3-helloworld/Makefile +++ b/part3-helloworld/Makefile @@ -1,19 +1,24 @@ CFILES = $(wildcard *.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin +BOOTMNT ?= /media/cvdeveloper/bootfs all: clean kernel8.img boot.o: boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c boot.S -o boot.o + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot.o $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8-rpi4.img + + cp kernel8-rpi4.img $(BOOTMNT)/kernel8-rpi4.img + cp config.txt $(BOOTMNT)/ + sync clean: /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part3-helloworld/Makefile.gcc b/part3-helloworld/Makefile.gcc deleted file mode 100644 index ae02b77..0000000 --- a/part3-helloworld/Makefile.gcc +++ /dev/null @@ -1,19 +0,0 @@ -CFILES = $(wildcard *.c) -OFILES = $(CFILES:.c=.o) -GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles -GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin - -all: clean kernel8.img - -boot.o: boot.S - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o - -%.o: %.c - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ - -kernel8.img: boot.o $(OFILES) - $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img - -clean: - /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part3-helloworld/config.txt b/part3-helloworld/config.txt new file mode 100644 index 0000000..42f2494 --- /dev/null +++ b/part3-helloworld/config.txt @@ -0,0 +1,46 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +kernel_old=1 +disable_commandline_tags=1 +arm_64bit=1 +core_freq_min=500 diff --git a/part4-miniuart/Makefile b/part4-miniuart/Makefile index 371b8b1..77a5836 100644 --- a/part4-miniuart/Makefile +++ b/part4-miniuart/Makefile @@ -1,19 +1,24 @@ CFILES = $(wildcard *.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin +BOOTMNT ?= /media/cvdeveloper/bootfs all: clean kernel8.img boot.o: boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c boot.S -o boot.o + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot.o $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img + + cp kernel8.img $(BOOTMNT)/kernel8.img + cp config.txt $(BOOTMNT)/ + sync clean: /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part4-miniuart/Makefile.gcc b/part4-miniuart/Makefile.gcc deleted file mode 100644 index ae02b77..0000000 --- a/part4-miniuart/Makefile.gcc +++ /dev/null @@ -1,19 +0,0 @@ -CFILES = $(wildcard *.c) -OFILES = $(CFILES:.c=.o) -GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles -GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin - -all: clean kernel8.img - -boot.o: boot.S - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o - -%.o: %.c - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ - -kernel8.img: boot.o $(OFILES) - $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img - -clean: - /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part4-miniuart/config.txt b/part4-miniuart/config.txt new file mode 100644 index 0000000..42f2494 --- /dev/null +++ b/part4-miniuart/config.txt @@ -0,0 +1,46 @@ +# For more options and information see +# http://rptl.io/configtxt +# Some settings may impact device functionality. See link above for details + +# Uncomment some or all of these to enable the optional hardware interfaces +#dtparam=i2c_arm=on +#dtparam=i2s=on +#dtparam=spi=on + +# Enable audio (loads snd_bcm2835) +dtparam=audio=on + +# Additional overlays and parameters are documented +# /boot/firmware/overlays/README + +# Automatically load overlays for detected cameras +camera_auto_detect=1 + +# Automatically load overlays for detected DSI displays +display_auto_detect=1 + +# Automatically load initramfs files, if found +auto_initramfs=1 + +# Enable DRM VC4 V3D driver +dtoverlay=vc4-kms-v3d +max_framebuffers=2 + +# Don't have the firmware create an initial video= setting in cmdline.txt. +# Use the kernel's default instead. +disable_fw_kms_setup=1 + +# Run in 64-bit mode +arm_64bit=1 + +# Disable compensation for displays with overscan +disable_overscan=1 + +# Run as fast as firmware / board allows +arm_boost=1 + +[all] +kernel_old=1 +disable_commandline_tags=1 +arm_64bit=1 +core_freq_min=500 diff --git a/part4-miniuart/io.c b/part4-miniuart/io.c index 9561780..d7b2985 100644 --- a/part4-miniuart/io.c +++ b/part4-miniuart/io.c @@ -17,8 +17,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part5-framebuffer/Makefile b/part5-framebuffer/Makefile index 371b8b1..ae02b77 100644 --- a/part5-framebuffer/Makefile +++ b/part5-framebuffer/Makefile @@ -1,19 +1,19 @@ CFILES = $(wildcard *.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin all: clean kernel8.img boot.o: boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c boot.S -o boot.o + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot.o $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean: /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part5-framebuffer/Makefile.gcc b/part5-framebuffer/Makefile.gcc deleted file mode 100644 index ae02b77..0000000 --- a/part5-framebuffer/Makefile.gcc +++ /dev/null @@ -1,19 +0,0 @@ -CFILES = $(wildcard *.c) -OFILES = $(CFILES:.c=.o) -GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles -GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin - -all: clean kernel8.img - -boot.o: boot.S - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o - -%.o: %.c - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ - -kernel8.img: boot.o $(OFILES) - $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img - -clean: - /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part5-framebuffer/io.c b/part5-framebuffer/io.c index e8af328..b58f996 100644 --- a/part5-framebuffer/io.c +++ b/part5-framebuffer/io.c @@ -18,8 +18,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part6-breakout/Makefile b/part6-breakout/Makefile index 371b8b1..ae02b77 100644 --- a/part6-breakout/Makefile +++ b/part6-breakout/Makefile @@ -1,19 +1,19 @@ CFILES = $(wildcard *.c) OFILES = $(CFILES:.c=.o) -LLVMPATH = /opt/homebrew/opt/llvm/bin -CLANGFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -mcpu=cortex-a72+nosimd +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin all: clean kernel8.img boot.o: boot.S - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c boot.S -o boot.o + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o %.o: %.c - $(LLVMPATH)/clang --target=aarch64-elf $(CLANGFLAGS) -c $< -o $@ + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ kernel8.img: boot.o $(OFILES) - $(LLVMPATH)/ld.lld -m aarch64elf -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(LLVMPATH)/llvm-objcopy -O binary kernel8.elf kernel8.img + $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img clean: /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part6-breakout/Makefile.gcc b/part6-breakout/Makefile.gcc deleted file mode 100644 index ae02b77..0000000 --- a/part6-breakout/Makefile.gcc +++ /dev/null @@ -1,19 +0,0 @@ -CFILES = $(wildcard *.c) -OFILES = $(CFILES:.c=.o) -GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles -GCCPATH = ../../gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin - -all: clean kernel8.img - -boot.o: boot.S - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o - -%.o: %.c - $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ - -kernel8.img: boot.o $(OFILES) - $(GCCPATH)/aarch64-none-elf-ld -nostdlib boot.o $(OFILES) -T link.ld -o kernel8.elf - $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img - -clean: - /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part6-breakout/io.c b/part6-breakout/io.c index e06b64d..c5134ab 100644 --- a/part6-breakout/io.c +++ b/part6-breakout/io.c @@ -18,8 +18,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part7-bluetooth/io.c b/part7-bluetooth/io.c index 8a591b7..33b3962 100644 --- a/part7-bluetooth/io.c +++ b/part7-bluetooth/io.c @@ -18,8 +18,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part8-breakout-ble/io.c b/part8-breakout-ble/io.c index 8a591b7..33b3962 100644 --- a/part8-breakout-ble/io.c +++ b/part8-breakout-ble/io.c @@ -18,8 +18,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; } diff --git a/part9-sound/io.c b/part9-sound/io.c index a2a888d..b9ba71f 100644 --- a/part9-sound/io.c +++ b/part9-sound/io.c @@ -19,8 +19,8 @@ enum { enum { Pull_None = 0, - Pull_Down = 1, // Are down and up the right way around? - Pull_Up = 2 + Pull_Down = 2, + Pull_Up = 1 }; void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; }