How does the Linux Kernel store hardware TX and RX filter modes?

I am working on a C program which gets the timestamping information for a given network interface, like my own version of ethtool. My goal is to get the information printed by $ ethtool -T myNetIf. Something like:

Time stamping parameters for myNetIf:
    hardware-transmit     (SOF_TIMESTAMPING_TX_HARDWARE)
    software-transmit     (SOF_TIMESTAMPING_TX_SOFTWARE)
    hardware-receive      (SOF_TIMESTAMPING_RX_HARDWARE)
    software-receive      (SOF_TIMESTAMPING_RX_SOFTWARE)
    software-system-clock (SOF_TIMESTAMPING_SOFTWARE)
    hardware-raw-clock    (SOF_TIMESTAMPING_RAW_HARDWARE)
PTP Hardware Clock: 0
Hardware Transmit Timestamp Modes:
    off                   (HWTSTAMP_TX_OFF)
    on                    (HWTSTAMP_TX_ON)
Hardware Receive Filter Modes:
    none                  (HWTSTAMP_FILTER_NONE)
    all                   (HWTSTAMP_FILTER_ALL)

Since I don’t want to just scrape the command line output, I have figured out that I can use an ioctl call to query this information from ethtool and get my flags back as an unsigned integer.

struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
struct ethtool_ts_info etsi;
memset(&etsi, 0, sizeof(struct ethtool_ts_info));

strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); // dev is read from stdin
ifr.ifr_data = (void *)&etsi;

ioctl(sock, SIOCETHTOOL, &ifr); // sock is an AF_INET socket

The ethtool_ts_info struct which (I believe) contains the data I want is defined here. Specifically, I grab the so_timestamping, tx_types, and rx_filters fields.

Using the same example interface, the value of so_timestamping is 0x5f, or 0101 1111. A look at net_tstamp.h confirms that these are the expected flags.

My issue, however, is in interpreting the values of tx_types and rx_filters. I assumed that the value of tx_types would be something like 0x3 (0011, where the 3rd bit is HWTSTAMP_TX_ON and the 4th is HWTSTAMP_TX_OFF). Alternatively, since the possible transmit modes are defined in an enum with 4 values, perhaps the result would be 0100, where each int enum value is 2 bits.

This is not the case. The actual value of tx_types is 0x7fdd. How on Earth am I supposed to get "HW_TX_OFF and ON" from 0x7fdd? I find the value of rx_filters even more confusing. What is 0x664758eb supposed to mean?

Besides the kernel source code itself, I haven’t been able to find much helpful information on this. I think I’ve done everything right, I just need some help understanding my results.

Asked By: Robbie


struct ethtool_ts_info etsi;

ifr.ifr_data = (void *)&etsi;

From the kernel documentation for network timesamping, I understand that the ioctl(SIOCSHWTSTAMP) your program calls is supposed to fill some struct of the type hwtstamp_config

having struct hwtstamp_config being defined as :

struct hwtstamp_config {
    int flags;  /* no flags defined right now, must be zero */
    int tx_type;    /* HWTSTAMP_TX_* */
    int rx_filter;  /* HWTSTAMP_FILTER_* */

Desired behavior is passed into the kernel and to a specific device by
calling ioctl(SIOCSHWTSTAMP) with a pointer to a struct ifreq whose
ifr_data points to a struct hwtstamp_config
… Any process can read
the actual configuration by passing this structure to
ioctl(SIOCGHWTSTAMP) in the same way.

When your program uses the data returned as if it was a struct ethtool_ts_info defined as :

struct ethtool_ts_info {
    __u32   cmd;
    __u32   so_timestamping;
    __s32   phc_index;
    __u32   tx_types;
    __u32   tx_reserved[3];
    __u32   rx_filters;
    __u32   rx_reserved[3];

This implying that the value your program would extract beyond the third int (and in particular tx_types and rx_filters you are interested in) are just, as you reported,… meaningless.
The (configuration iow not capabilities) values for tx_types and rx_filters should be fetched at the second and third int respectively starting from ifr.ifr_data address instead.

In the case, configuration values are not what you are looking for then, ioctl-ing(SIOCGHWTSTAMP) is not the appropriate way to go.

Answered By: MC68020

This is embarrassing: I have reviewed my code and found that I did fail to properly initialize a struct, just not in quite the way I expected. I also didn’t provide the full context needed to solve the problem, since I thought I had ruled out the possibility of junk values left over from before allocation.

The sample code I posted is part of a function which fills out a struct I’ve defined containing the relevant fields of ethtool_ts_info.

typedef struct {
    unsigned int soTimestamping;
    unsigned int txTypes;
    unsigned int rxFilters;
} tsCapsFilters_t;

The user of the function initializes this struct themselves and the function merely populates it. So expected usage looks like this:

tsCapsFilters_t tsInfo; // struct full of junk
if(getTsInfo(nameOfNetIf, &tsInfo, errMsgBuf, ERR_BUF_SZ) < 0) {
    // handle errors in here
printf("%xn", tsInfo.soTimestamping); // struct filled out by getTsInfo

However, the mistake I made was that I forgot to copy tx_types and rx_filters from the ethtool_ts_info struct (internal to my function) to the tsCapsFilters_t struct (given to the user). So my program got all the fields just fine, but it only returned the so_timestamping capabilities when it was supposed to return all 3 values.

printf("%xn", tsInfo.soTimestamping); // printed the expected values
printf("%xn", tsInfo.txTypes); // printed junk
printf("%xn", tsInfo.rxFilters); // printed junk

Like I said, embarrassing. I’ll award the bounty and be more careful with my structs in the future.

Answered By: Robbie