ch32v307 dev board, part 6

In this post, I'll cover adding ipv6 support to the ch32v307 development board.

This is part of a series on the ch32v307 dev board. The previous post was solving the flash/ram partition so LWIP would have enough space to run.

Like the situation for the Teensy, I wanted IPv6 support on the ch32v307. The ethernet hardware is designed differently on those two processors. To explain their differences, I'll start with describing what they are trying to do.

When connected to an ethernet network, there will be traffic that the host can completely ignore. Back when hubs were a thing, this would include traffic from individual hosts talking to other hosts/routers. But now that switches have taken over, this is primarily multicast and unknown unicast traffic. Broadcast traffic is difficult to filter at the hardware level with common NICs, so we'll let the software handle that.

Different NICs have a variety of ways to filter traffic, and the most common ways are telling it which destination MAC addresses you're interested in. The Teensy hardware has a mac address filter based on a hash table.

The ch32v307 has both a hash table and a setting for 4 specific mac addresses. An exact match will be better at only allowing the traffic we're interested in, so we'll use that to allow the multicast MAC addresses through the hardware filters.

First we need to register a function to handle the hardware MAC address filters with LWIP:

#if LWIP_IPV6_MLD
    netif->flags |= NETIF_FLAG_MLD6;
    netif->mld_mac_filter = ethernet_mld_mac_filter;
#endif

The function to manage multicast addresses must first translate IPv6 multicast addresses to their MAC address equivilent:

err_t ethernet_mld_mac_filter(
  struct netif *netif,
  const ip6_addr_t *group,
  enum netif_mac_filter_action action
) {
    uint8_t address[6];
    ip6_to_multicast(group, address);

Translating an IPv6 multicast address into a MAC address is pretty simple:

static void ip6_to_multicast(const ip6_addr_t *group, uint8_t *mac_address) {
    // multicast mac address is 33:33:[lowest 32 bits of v6 address]
    mac_address[0] = 0x33;
    mac_address[1] = 0x33;
    memcpy(mac_address + 2, group->addr + 3, 4);
}

If the filter is being added, the code should look through all the 4 filter slots to see if there are any free:

    for(uint8_t i = 0; i < 4; i++) {
        if(used_filters & (1 << i)) {
            // skip any used filter
            continue;
        }
        // ... [see below] ...
    }

If a free slot is found, use it. Mark it as used so it doesn't get overwritten:

        uint32_t eth_mac = i_to_eth_mac_address(i);
        ETH_MACAddressConfig(eth_mac, address);
        ETH_MACAddressPerfectFilterCmd(eth_mac, ENABLE);
        used_filters |= 1 << i;
        return ERR_OK;

For the case where we're removing a MAC address from the filter, we should find all the active filter slots:

    if (action == NETIF_DEL_MAC_FILTER) {
        for(uint8_t i = 0; i < 4; i++) {
            if(!(used_filters & (1 << i))) {
                // skip any unused filter
                continue;
            }
            // ... [see below] ...
        }
    }

Check the active filter slots if they're for the given address:

            uint32_t eth_mac = i_to_eth_mac_address(i);
            ETH_GetMACAddress(eth_mac, config_mac);
            if(memcmp(address, config_mac, sizeof(address)) == 0) {
                // ... [see below] ...
            }

If they are, then disable the slot and mark it as free:

                ETH_MACAddressPerfectFilterCmd(eth_mac, DISABLE);
                used_filters &= ~(1 << i);
                return ERR_OK;

That solves the problem of allowing the proper multicast packets into LWIP to process.

Let's use it to listen on the all nodes MAC address for IPv6 route announcements:

#if LWIP_IPV6_MLD
    ip6_addr_t ip6_allnodes_ll;
    ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);
    netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);
#endif

And then the last thing I'll do is to use SLAAC to configure our link local and global addresses:

#if LWIP_IPV6
        netif_create_ip6_linklocal_address(&WCH_NetIf, 1);
        netif_set_ip6_autoconfig_enabled(&WCH_NetIf, 1);
#endif

Then I can verify via the serial port that the ch32v307 has IPv6 addresses configured:

> ip
IP 10.42.0.172
IP6 FE80::5254:7BFF:FE45:4540 s=30
IP6 2600:1700:xx:xx:5254:7BFF:FE45:4540 s=30
IP6 :: s=0

And I can ping those addresses from another host on the network:

$ ping6 2600:1700:xx:xx:5254:7BFF:FE45:4540
PING 2600:1700:xx:xx:5254:7BFF:FE45:4540(2600:1700:xx:xx:5254:7bff:fe45:4540) 56 data bytes
64 bytes from 2600:1700:xx:xx:5254:7bff:fe45:4540: icmp_seq=1 ttl=255 time=1.40 ms
64 bytes from 2600:1700:xx:xx:5254:7bff:fe45:4540: icmp_seq=2 ttl=255 time=0.682 ms
64 bytes from 2600:1700:xx:xx:5254:7bff:fe45:4540: icmp_seq=3 ttl=255 time=0.734 ms