unix: add IoctlGetEthtoolTsInfo on Linux

The function fetches ethtool timestamping and PHC association for a
network interface. Its primary usage is to query the mapping between
the interface and its corresponding PTP clock number in /dev/ptp𝑛.

Change-Id: Id09466b3b43056c628593d4d2e05d77ec8d8082b
GitHub-Last-Rev: 3743a3a6504e6926031b8f2ece331078ae543b25
GitHub-Pull-Request: golang/sys#222
Reviewed-on: https://go-review.googlesource.com/c/sys/+/619335
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Auto-Submit: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
diff --git a/unix/ioctl_linux.go b/unix/ioctl_linux.go
index dbe680e..e605abd 100644
--- a/unix/ioctl_linux.go
+++ b/unix/ioctl_linux.go
@@ -58,6 +58,21 @@
 	return &value, err
 }
 
+// IoctlGetEthtoolTsInfo fetches ethtool timestamping and PHC
+// association for the network device specified by ifname.
+func IoctlGetEthtoolTsInfo(fd int, ifname string) (*EthtoolTsInfo, error) {
+	ifr, err := NewIfreq(ifname)
+	if err != nil {
+		return nil, err
+	}
+
+	value := EthtoolTsInfo{Cmd: ETHTOOL_GET_TS_INFO}
+	ifrd := ifr.withData(unsafe.Pointer(&value))
+
+	err = ioctlIfreqData(fd, SIOCETHTOOL, &ifrd)
+	return &value, err
+}
+
 // IoctlGetWatchdogInfo fetches information about a watchdog device from the
 // Linux watchdog API. For more information, see:
 // https://www.kernel.org/doc/html/latest/watchdog/watchdog-api.html.
diff --git a/unix/linux/types.go b/unix/linux/types.go
index 0ba570f..61e82e5 100644
--- a/unix/linux/types.go
+++ b/unix/linux/types.go
@@ -4090,6 +4090,8 @@
 
 type EthtoolDrvinfo C.struct_ethtool_drvinfo
 
+type EthtoolTsInfo C.struct_ethtool_ts_info
+
 type (
 	HIDRawReportDescriptor C.struct_hidraw_report_descriptor
 	HIDRawDevInfo          C.struct_hidraw_devinfo
diff --git a/unix/syscall_linux_test.go b/unix/syscall_linux_test.go
index 53e6445..eca3b7a 100644
--- a/unix/syscall_linux_test.go
+++ b/unix/syscall_linux_test.go
@@ -68,6 +68,44 @@
 	}
 }
 
+func TestIoctlGetEthtoolTsInfo(t *testing.T) {
+	if runtime.GOOS == "android" {
+		t.Skip("ethtool driver info is not available on android, skipping test")
+	}
+
+	s, err := unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0)
+	if err != nil {
+		t.Fatalf("failed to open socket: %v", err)
+	}
+	defer unix.Close(s)
+
+	ifis, err := net.Interfaces()
+	if err != nil {
+		t.Fatalf("failed to get network interfaces: %v", err)
+	}
+
+	// Print the interface name and associated PHC information for each
+	// network interface supported by ethtool.
+	for _, ifi := range ifis {
+		tsi, err := unix.IoctlGetEthtoolTsInfo(s, ifi.Name)
+		if err != nil {
+			if err == unix.EOPNOTSUPP {
+				continue
+			}
+
+			if err == unix.EBUSY {
+				// See https://go.dev/issues/67350
+				t.Logf("%s: ethtool driver busy, possible kernel bug", ifi.Name)
+				continue
+			}
+
+			t.Fatalf("failed to get ethtool PHC info for %q: %v", ifi.Name, err)
+		}
+
+		t.Logf("%s: ptp%d", ifi.Name, tsi.Phc_index)
+	}
+}
+
 func TestIoctlGetInt(t *testing.T) {
 	f, err := os.Open("/dev/random")
 	if err != nil {
diff --git a/unix/ztypes_linux.go b/unix/ztypes_linux.go
index 3a69e45..232c379 100644
--- a/unix/ztypes_linux.go
+++ b/unix/ztypes_linux.go
@@ -4110,6 +4110,16 @@
 	Regdump_len  uint32
 }
 
+type EthtoolTsInfo struct {
+	Cmd             uint32
+	So_timestamping uint32
+	Phc_index       int32
+	Tx_types        uint32
+	Tx_reserved     [3]uint32
+	Rx_filters      uint32
+	Rx_reserved     [3]uint32
+}
+
 type (
 	HIDRawReportDescriptor struct {
 		Size  uint32