From nobody Wed May 1 01:10:25 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1608327830; cv=none; d=zohomail.com; s=zohoarc; b=nvrW/mJLN6sqLJyYd5AHgDcw0DoJpq28qcbH+8c1xP9pzIxkCYrDZn5fUqsHtay3Ey8nxYA3xao7qE7RvePYz++NJiMfvn4e44NILlSyy1nvR30ikvaKxNM6N1Oeidw8UWiQ06ds9QHDWjCo+ow40f5z8e1f5m48ULxPd06dZ+I= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1608327830; h=Content-Type:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=F9ij6hk39jR3TiX6rDaamQjpgRj/1PFdAYOdk/a8OeU=; b=Ubi2RBDm7jEt9w/yFhOVdZWlG9BEOw+UYfd/cfdp/GTV3T/kkskqbeGYiKlvPzZ48pRm4YalJUJFm4Gu4LfeYQXe4rVwLCEmhJlVxqXdHK253HvYeGv4c4dS0A+0GjXWNDCFmo830nRF6LBODUupCVIpOiudJTGNS4majmm9pss= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1608327830046808.8927943304818; Fri, 18 Dec 2020 13:43:50 -0800 (PST) Received: from localhost ([::1]:51596 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kqNXQ-0002Qc-W6 for importer@patchew.org; Fri, 18 Dec 2020 16:43:49 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:36472) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3HCLdXwMKCtkN7RBJJBG9.7JHL9HP-89Q9GIJIBIP.JMB@flex--scw.bounces.google.com>) id 1kqNVZ-0001H5-Cb for qemu-devel@nongnu.org; Fri, 18 Dec 2020 16:41:53 -0500 Received: from mail-qv1-xf4a.google.com ([2607:f8b0:4864:20::f4a]:38840) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3HCLdXwMKCtkN7RBJJBG9.7JHL9HP-89Q9GIJIBIP.JMB@flex--scw.bounces.google.com>) id 1kqNVW-0002a2-NI for qemu-devel@nongnu.org; Fri, 18 Dec 2020 16:41:52 -0500 Received: by mail-qv1-xf4a.google.com with SMTP id u8so2959970qvm.5 for ; Fri, 18 Dec 2020 13:41:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=sender:date:in-reply-to:message-id:mime-version:references:subject :from:to:cc; bh=F9ij6hk39jR3TiX6rDaamQjpgRj/1PFdAYOdk/a8OeU=; b=q8g/RPzXYLzuOMAQHxmiw5Q+AEHXDTnGDJI0g8wV3t6OrtD2L0Aat56LFgdIyco3Nu CVr8s6cJA8hBv2EuPeB0ReKFUJcfTJMzPJIunZfLb3TxbRxz0WFPNSUQZe1ukuJOMYMm qsxDTCRvizZ0Btc/bdY2Kp7N+bSMUkZs6FOK6u4hMQ4fYAL7AQCxewRJNHdfGC7SAZaG qLSQDLOxUtPXhwRft7/2wVY4R3s+j/N3B5XinLI4BoJjt+nK89CWwlgWRamQy7FWmzUg rPkEa+EwlWEU6Rj4dGku1IT3Bvo8R8LusJC6OEVnt1/qf1Yj39JqLT1meKeM3MBEJ5+H VcAQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=F9ij6hk39jR3TiX6rDaamQjpgRj/1PFdAYOdk/a8OeU=; b=HK8y2bfBY4viytdHGui2DgQqq02KrM94aksgEtRofX97idaIVRZ6S7SUgTwfqytt1T WtXG9ICNMiN3HIeaFSKr1Ipbqv3XCgnInvhl7sUJ8K2DYQiHBIaKexghpgYBjJLAF+p3 EAt1QuuRdABMqEXj9p7DlsuVGfR6hhIlp8C2WjF2lUfXjh2c3Ts1droVa9EW2GMezUQM vRGeKQVGkzP2IqMcIOxk5eFfIMMtySw06CUAO9jgjtsmZZfa+jzSakrTH4Surr1peT5T vZriPLiTS+DzRsO5t9HjTapmzU99Oi/A3XdTD4Cgj7RqEfEnJjWxSa/LcUo0ytk3zHLe x9nA== X-Gm-Message-State: AOAM532fLMjunAV2m2QiemN92CYzDgUfxsDxVZtnlR9y6Ukw7yxsq2QE 2ArtQOmq7lrcULNcFy4TU7Jp6QDMMQSIR1ho4WNc5BojdLezd1Dj0MXslrIljFvmz4noElZpVma x1VDpo/yaD8M3VYq++USCgtchGxUjHJW/+v+TVGRo5PjRY2FcfjnH X-Google-Smtp-Source: ABdhPJxoxXZG+RIwPW/qsW8nKiTLQoT9hM++Ufbw2L4G2hY2SgrgqybxNaK40KaTjy05P+ny1wjrBq4= X-Received: from scw-glinux.svl.corp.google.com ([2620:15c:2ce:200:f693:9fff:fef4:29b5]) (user=scw job=sendgmr) by 2002:a0c:b990:: with SMTP id v16mr2964028qvf.16.1608327708650; Fri, 18 Dec 2020 13:41:48 -0800 (PST) Date: Fri, 18 Dec 2020 13:41:41 -0800 In-Reply-To: <20201218214142.3673709-1-scw@google.com> Message-Id: <20201218214142.3673709-2-scw@google.com> Mime-Version: 1.0 References: <20201218214142.3673709-1-scw@google.com> X-Mailer: git-send-email 2.29.2.684.gfbc64c5ab5-goog Subject: [PATCH 1/2] thunk: supports flexible arrays To: qemu-devel@nongnu.org Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::f4a; envelope-from=3HCLdXwMKCtkN7RBJJBG9.7JHL9HP-89Q9GIJIBIP.JMB@flex--scw.bounces.google.com; helo=mail-qv1-xf4a.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Riku Voipio , Shu-Chun Weng Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" Reply-to: Shu-Chun Weng From: Shu-Chun Weng via X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Flexible arrays may appear in the last field of a struct and are heavily used in the ioctl(SIOCETHTOOL) system call on Linux. E.g. struct ethtool_regs { __u32 cmd; __u32 version; /* driver-specific, indicates different chips/revs */ __u32 len; /* bytes */ __u8 data[0]; }; where number of elements in `data` is specified in `len`. It is translated into: STRUCT(ethtool_regs, TYPE_INT, /* cmd */ TYPE_INT, /* version */ TYPE_INT, /* len */ MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ where the "2" passed to `MK_FLEXIBLE_ARRAY` means the number of element is specified by field number 2 (0-index). Signed-off-by: Shu-Chun Weng --- include/exec/user/thunk.h | 24 ++++++ thunk.c | 152 +++++++++++++++++++++++++++++++++++++- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/include/exec/user/thunk.h b/include/exec/user/thunk.h index 300a840d58..42c58db7f0 100644 --- a/include/exec/user/thunk.h +++ b/include/exec/user/thunk.h @@ -39,12 +39,21 @@ typedef enum argtype { TYPE_ARRAY, TYPE_STRUCT, TYPE_OLDDEVT, + TYPE_FLEXIBLE_ARRAY, } argtype; =20 #define MK_PTR(type) TYPE_PTR, type #define MK_ARRAY(type, size) TYPE_ARRAY, (int)(size), type #define MK_STRUCT(id) TYPE_STRUCT, id =20 +/* + * Should only appear as the last element of a TYPE_STRUCT. `len_field_idx= ` is + * the index into the fields in the enclosing struct that specify the leng= th of + * the flexibly array. The length field MUST be a TYPE_INT field. + */ +#define MK_FLEXIBLE_ARRAY(type, len_field_idx) \ + TYPE_FLEXIBLE_ARRAY, (len_field_idx), type + #define THUNK_TARGET 0 #define THUNK_HOST 1 =20 @@ -56,6 +65,8 @@ typedef struct { /* special handling */ void (*convert[2])(void *dst, const void *src); void (*print)(void *arg); + int (*thunk_size[2])(const void *src); + int size[2]; int align[2]; const char *name; @@ -76,6 +87,11 @@ const argtype *thunk_convert(void *dst, const void *src, const argtype *type_ptr, int to_host); const argtype *thunk_print(void *arg, const argtype *type_ptr); =20 +bool thunk_type_has_flexible_array(const argtype *type_ptr); +/* thunk_type_size but can handle TYPE_FLEXIBLE_ARRAY */ +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, + int is_host); + extern StructEntry *struct_entries; =20 int thunk_type_size_array(const argtype *type_ptr, int is_host); @@ -138,6 +154,12 @@ static inline int thunk_type_size(const argtype *type_= ptr, int is_host) case TYPE_STRUCT: se =3D struct_entries + type_ptr[1]; return se->size[is_host]; + case TYPE_FLEXIBLE_ARRAY: + /* + * Flexible arrays do not count toward sizeof(). Users of structur= es + * containing them need to calculate it themselves. + */ + return 0; default: g_assert_not_reached(); } @@ -188,6 +210,8 @@ static inline int thunk_type_align(const argtype *type_= ptr, int is_host) case TYPE_STRUCT: se =3D struct_entries + type_ptr[1]; return se->align[is_host]; + case TYPE_FLEXIBLE_ARRAY: + return thunk_type_align_array(type_ptr + 2, is_host); default: g_assert_not_reached(); } diff --git a/thunk.c b/thunk.c index fc5be1a502..f13e96cc4f 100644 --- a/thunk.c +++ b/thunk.c @@ -50,6 +50,8 @@ static inline const argtype *thunk_type_next(const argtyp= e *type_ptr) return thunk_type_next_ptr(type_ptr + 1); case TYPE_STRUCT: return type_ptr + 1; + case TYPE_FLEXIBLE_ARRAY: + return thunk_type_next_ptr(type_ptr + 1); default: return NULL; } @@ -122,6 +124,34 @@ void thunk_register_struct_direct(int id, const char *= name, se->name =3D name; } =20 +static const argtype * +thunk_convert_flexible_array(void *dst, const void *src, + const uint8_t *dst_struct, + const uint8_t *src_struct, const argtype *typ= e_ptr, + const StructEntry *se, int to_host) { + int len_field_idx, dst_size, src_size, i; + uint32_t array_length; + uint8_t *d; + const uint8_t *s; + + assert(*type_ptr =3D=3D TYPE_FLEXIBLE_ARRAY); + type_ptr++; + len_field_idx =3D *type_ptr++; + array_length =3D + *(const uint32_t *)(to_host ? + dst_struct + se->field_offsets[1][len_field_id= x] : + src_struct + se->field_offsets[0][len_field_id= x]); + dst_size =3D thunk_type_size(type_ptr, to_host); + src_size =3D thunk_type_size(type_ptr, to_host); + d =3D dst; + s =3D src; + for (i =3D 0; i < array_length; i++) { + thunk_convert(d, s, type_ptr, to_host); + d +=3D dst_size; + s +=3D src_size; + } + return thunk_type_next(type_ptr); +} =20 /* now we can define the main conversion functions */ const argtype *thunk_convert(void *dst, const void *src, @@ -246,7 +276,7 @@ const argtype *thunk_convert(void *dst, const void *src, =20 assert(*type_ptr < max_struct_entries); se =3D struct_entries + *type_ptr++; - if (se->convert[0] !=3D NULL) { + if (se->convert[to_host] !=3D NULL) { /* specific conversion is needed */ (*se->convert[to_host])(dst, src); } else { @@ -256,7 +286,18 @@ const argtype *thunk_convert(void *dst, const void *sr= c, src_offsets =3D se->field_offsets[1 - to_host]; d =3D dst; s =3D src; - for(i =3D 0;i < se->nb_fields; i++) { + for (i =3D 0; i < se->nb_fields; i++) { + if (*field_types =3D=3D TYPE_FLEXIBLE_ARRAY) { + field_types =3D thunk_convert_flexible_array( + d + dst_offsets[i], + s + src_offsets[i], + d, + s, + field_types, + se, + to_host); + continue; + } field_types =3D thunk_convert(d + dst_offsets[i], s + src_offsets[i], field_types, to_host); @@ -264,6 +305,11 @@ const argtype *thunk_convert(void *dst, const void *sr= c, } } break; + case TYPE_FLEXIBLE_ARRAY: + fprintf(stderr, + "Invalid flexible array (type 0x%x) outside of a structure= \n", + type); + break; default: fprintf(stderr, "Invalid type 0x%x\n", type); break; @@ -271,6 +317,45 @@ const argtype *thunk_convert(void *dst, const void *sr= c, return type_ptr; } =20 +static const argtype * +thunk_print_flexible_array(void *arg, const uint8_t *arg_struct, + const argtype *type_ptr, const StructEntry *se)= { + int array_length, len_field_idx, arg_size, i; + uint8_t *a; + int is_string =3D 0; + + assert(*type_ptr =3D=3D TYPE_FLEXIBLE_ARRAY); + type_ptr++; + len_field_idx =3D *type_ptr++; + + array_length =3D tswap32( + *(const uint32_t *)(arg_struct + se->field_offsets[0][len_field_id= x])); + arg_size =3D thunk_type_size(type_ptr, 0); + a =3D arg; + + if (*type_ptr =3D=3D TYPE_CHAR) { + qemu_log("\""); + is_string =3D 1; + } else { + qemu_log("["); + } + + for (i =3D 0; i < array_length; i++) { + if (i > 0 && !is_string) { + qemu_log(","); + } + thunk_print(a, type_ptr); + a +=3D arg_size; + } + + if (is_string) { + qemu_log("\""); + } else { + qemu_log("]"); + } + return thunk_type_next(type_ptr); +} + const argtype *thunk_print(void *arg, const argtype *type_ptr) { int type; @@ -418,18 +503,81 @@ const argtype *thunk_print(void *arg, const argtype *= type_ptr) if (i > 0) { qemu_log(","); } + if (*field_types =3D=3D TYPE_FLEXIBLE_ARRAY) { + field_types =3D thunk_print_flexible_array( + a + arg_offsets[i], a, field_types, se); + continue; + } field_types =3D thunk_print(a + arg_offsets[i], field_= types); } qemu_log("}"); } } break; + case TYPE_FLEXIBLE_ARRAY: + fprintf(stderr, + "Invalid flexible array (type 0x%x) outside of a structure= \n", + type); + break; default: g_assert_not_reached(); } return type_ptr; } =20 +bool thunk_type_has_flexible_array(const argtype *type_ptr) +{ + int i; + const StructEntry *se; + const argtype *field_types; + if (*type_ptr !=3D TYPE_STRUCT) { + return false; + } + se =3D struct_entries + type_ptr[1]; + field_types =3D se->field_types; + for (i =3D 0; i < se->nb_fields; i++) { + if (*field_types =3D=3D TYPE_FLEXIBLE_ARRAY) { + return true; + } + field_types =3D thunk_type_next(type_ptr); + } + return false; +} + +int thunk_type_size_with_src(const void *src, const argtype *type_ptr, + int is_host) +{ + switch (*type_ptr) { + case TYPE_STRUCT: { + int i; + const StructEntry *se =3D struct_entries + type_ptr[1]; + const argtype *field_types; + if (se->thunk_size[is_host] !=3D NULL) { + return (*se->thunk_size[is_host])(src); + } + + field_types =3D se->field_types; + for (i =3D 0; i < se->nb_fields; i++) { + if (*field_types =3D=3D TYPE_FLEXIBLE_ARRAY) { + uint32_t array_length =3D *(const uint32_t *)( + (const uint8_t *)src + + se->field_offsets[is_host][field_types[1]]); + if (!is_host) { + array_length =3D tswap32(array_length); + } + return se->size[is_host] + + array_length * + thunk_type_size(field_types + 2, is_host); + } + field_types =3D thunk_type_next(type_ptr); + } + return se->size[is_host]; + } + default: + return thunk_type_size(type_ptr, is_host); + } +} + /* from em86 */ =20 /* Utility function: Table-driven functions to translate bitmasks --=20 2.29.2.684.gfbc64c5ab5-goog From nobody Wed May 1 01:10:25 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1608327962; cv=none; d=zohomail.com; s=zohoarc; b=nxPuUPho7CdsIOIu840et8pn61bET/dkBPXTilnybwryB+yITacNrTPs+vE3w8M1T4U99OpnnIqViT8f6n3h7uKtEg1AuJY5K798uuLR2crLmHm8jifC5Rrh7xWsUI5+LEIxUG8xg2pLOIcfuodRQjL8V6lEyKUFQ6i6LIoXztE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1608327962; h=Content-Type:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=TKUFE6CugCpKKKbORrlpa03o0SxlYT1o7uUdwEHhA1E=; b=nIXmo4JVKb/zgzm+q35sN5NR+XPLnbKCAk+erol5MnDwXOk3tAtXdOH4abst0xMyhea/Dt/PlPdoDNXY8t/+SqyvlN2wjceqwDfIGAPSUkZ9/3cfalxC3ixw+1zIJaWRhbwsRdZXbXy3nd5EAwO9I1BGf+V7rM9ZvO5K74964z8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1608327962887655.1320086238479; Fri, 18 Dec 2020 13:46:02 -0800 (PST) Received: from localhost ([::1]:54720 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kqNZZ-0003nG-IR for importer@patchew.org; Fri, 18 Dec 2020 16:46:01 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]:36488) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <3HiLdXwMKCtsP9TDLLDIB.9LJNBJR-ABSBIKLKDKR.LOD@flex--scw.bounces.google.com>) id 1kqNVe-0001Ie-BU for qemu-devel@nongnu.org; Fri, 18 Dec 2020 16:41:59 -0500 Received: from mail-yb1-xb49.google.com ([2607:f8b0:4864:20::b49]:33530) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <3HiLdXwMKCtsP9TDLLDIB.9LJNBJR-ABSBIKLKDKR.LOD@flex--scw.bounces.google.com>) id 1kqNVY-0002aR-Sh for qemu-devel@nongnu.org; Fri, 18 Dec 2020 16:41:58 -0500 Received: by mail-yb1-xb49.google.com with SMTP id a206so4959093ybg.0 for ; Fri, 18 Dec 2020 13:41:51 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=sender:date:in-reply-to:message-id:mime-version:references:subject :from:to:cc; bh=TKUFE6CugCpKKKbORrlpa03o0SxlYT1o7uUdwEHhA1E=; b=pHTBoJshj6hAwnT4216XOMELTk7OyEcGIdpc6jUzDceFnM5J8qm1hhuBUH9oXiBcYb CAnHZmaQpqlZI4dl6b08U+8Se3zYfoihVzSZDxFjj0JhSMi1f7NuEV8w3MY1XAMQFy3V T/4NipQoDdOaDJLeMJQsimiAVKPJcC+2B/1JF6Y0HC/7GkSxmC+/o14uWqBjGuihnv38 olIdbbD94HR9Vv+GeVOVj+viYrTadnEt/m0secSDg6SHXeUbQTsIm0JgNEC3t9TXKCPa bQxj/jut2Do+5O34OGT0nDRTfmaaDdLjw62c2RmhZtxABsrYCOe26hVyvdE4K5g/gNnw 5Ijw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:date:in-reply-to:message-id:mime-version :references:subject:from:to:cc; bh=TKUFE6CugCpKKKbORrlpa03o0SxlYT1o7uUdwEHhA1E=; b=MBoZCeH/B2BdEEFohuX4hyQjPVKhxkNqK9PgJL6stTFKNW2DOB9SiwA5NFhwdEn1QR Tj/WlOUCKY5idjN4Kv5PdEf6zVMYWnqHfmNkmSbF4X+AD4mxDJNSku4ME3raGt3O4ILF HhWbhgFGRoXBXH8aClGymiEbwrcqTTkeayF8lDx1JMMfDZHMH6USs7WVo9/tTX4HVW1P 6lnQDZFaf/yUKRQfE9ZVgU+Nb1rYZsy5JnnazAYXCMnP4ySRZsaskmOBPMGATbq9Ox35 WQuhNGw5nj/U+aEbpOPdUHBvdIg7jHm1elbzCSv/m4VO+L7HLZscz0tHbyMOOwlKZozn H40Q== X-Gm-Message-State: AOAM5308F723+C2XZ2630XjOaMaQdn/6tQ1AO5HnJBhRN3m28SBOM8IE 7al0nwQmhNu9ZOXbwSyeh5es2aRg5ZTuoWbehTdTfF7LaTl1FcyidVqVv8AvSH+zrGNjxE8KQLS D/Q+Sxd768r8wWKliuY48Fe09bYnmHvH5KODi1phlmdCcWO1ypJGu X-Google-Smtp-Source: ABdhPJyKsRdXn9AVqnJY5N93AB+JeSJ9qns4Ch8EaSNiaBKoadK0Ke+hU6jT6aoPhDHNaK7pkRN4QHo= X-Received: from scw-glinux.svl.corp.google.com ([2620:15c:2ce:200:f693:9fff:fef4:29b5]) (user=scw job=sendgmr) by 2002:a25:260f:: with SMTP id m15mr9507101ybm.43.1608327710546; Fri, 18 Dec 2020 13:41:50 -0800 (PST) Date: Fri, 18 Dec 2020 13:41:42 -0800 In-Reply-To: <20201218214142.3673709-1-scw@google.com> Message-Id: <20201218214142.3673709-3-scw@google.com> Mime-Version: 1.0 References: <20201218214142.3673709-1-scw@google.com> X-Mailer: git-send-email 2.29.2.684.gfbc64c5ab5-goog Subject: [PATCH 2/2] linux-user: Add support for SIOCETHTOOL ioctl To: qemu-devel@nongnu.org Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::b49; envelope-from=3HiLdXwMKCtsP9TDLLDIB.9LJNBJR-ABSBIKLKDKR.LOD@flex--scw.bounces.google.com; helo=mail-yb1-xb49.google.com X-Spam_score_int: -95 X-Spam_score: -9.6 X-Spam_bar: --------- X-Spam_report: (-9.6 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_MED=-0.001, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, USER_IN_DEF_DKIM_WL=-7.5 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Shu-Chun Weng , Laurent Vivier Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" Reply-to: Shu-Chun Weng From: Shu-Chun Weng via X-ZohoMail-DKIM: fail (Header signature does not verify) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The ioctl numeric values are platform-independent and determined by the file include/uapi/linux/sockios.h in Linux kernel source code: #define SIOCETHTOOL 0x8946 These ioctls get (or set) various structures pointed by the field ifr_data in the structure ifreq depending on the first 4 bytes of the memory region. This change clones the ioctl framework into ethtool-specific dispatch logic in its own file. A number of definitions previously only visible in syscall.c are thus exported to syscall_defs.h to be used in the new files. Signed-off-by: Shu-Chun Weng --- linux-user/ethtool.c | 848 ++++++++++++++++++++++++++++++++++ linux-user/ethtool.h | 20 + linux-user/ethtool_entries.h | 107 +++++ linux-user/ioctls.h | 2 + linux-user/meson.build | 1 + linux-user/qemu.h | 1 + linux-user/syscall.c | 36 +- linux-user/syscall_defs.h | 12 + linux-user/syscall_types.h | 280 +++++++++++ tests/tcg/multiarch/ethtool.c | 423 +++++++++++++++++ 10 files changed, 1719 insertions(+), 11 deletions(-) create mode 100644 linux-user/ethtool.c create mode 100644 linux-user/ethtool.h create mode 100644 linux-user/ethtool_entries.h create mode 100644 tests/tcg/multiarch/ethtool.c diff --git a/linux-user/ethtool.c b/linux-user/ethtool.c new file mode 100644 index 0000000000..6f6b84153a --- /dev/null +++ b/linux-user/ethtool.c @@ -0,0 +1,848 @@ +/* + * Linux ioctl system call SIOCETHTOOL requests + * + * Copyright (c) 2020 Shu-Chun Weng + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ +#include "qemu/osdep.h" +#include +#include +#include +#include +#include +#ifdef HAVE_BTRFS_H +/* + * Presence of the BTRFS macros affects the STRUCT_* macro values because = the + * order in syscall_types.h matters but some entries are dropped when the = BTRFS + * macros are not defined. We must include linux/btrfs.h before any file f= irst + * transitively include syscall_types.h. + */ +#include +#endif +#include "ethtool.h" +#include "qemu.h" + +/* Non-standard ethtool structure definitions. */ +/* + * struct ethtool_rxnfc { + * __u32 cmd; + * __u32 flow_type; + * __u64 data; + * struct ethtool_rx_flow_spec fs; + * union { + * __u32 rule_cnt; + * __u32 rss_context; + * }; + * __u32 rule_locs[0]; + * }; + * + * Originally defined for ETHTOOL_{G,S}RXFH with only the cmd, flow_type a= nd + * data members. For other commands, dedicated standard structure definiti= ons + * are listed in syscall_types.h. + */ +static void host_to_target_ethtool_rxnfc_get_set_rxfh(void *dst, + const void *src) +{ + static const argtype ethtool_rx_flow_spec_argtype[] =3D { + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL }; + struct ethtool_rxnfc *target =3D dst; + const struct ethtool_rxnfc *host =3D src; + + target->cmd =3D tswap32(host->cmd); + target->flow_type =3D tswap32(host->flow_type); + target->data =3D tswap64(host->data); + + if (host->cmd =3D=3D ETHTOOL_SRXFH) { + /* + * struct ethtool_rxnfc was originally defined for ETHTOOL_{G,S}RX= FH + * with only the cmd, flow_type and data members. Guest program mi= ght + * still be using that definition. + */ + return; + } + if (host->cmd !=3D ETHTOOL_GRXFH) { + fprintf(stderr, "host_to_target_ethtool_rxnfc_get_set_rxfh called = with " + "command 0x%x which is not ETHTOOL_SRXFH or ETHTOOL_GRXFH\= n", + host->cmd); + } + if ((host->flow_type & FLOW_RSS) =3D=3D 0) { + return; + } + /* + * If `FLOW_RSS` was requested then guest program must be using the new + * definition. + */ + thunk_convert(&target->fs, &host->fs, ethtool_rx_flow_spec_argtype, + THUNK_TARGET); + target->rule_cnt =3D tswap32(host->rule_cnt); +} + +static void target_to_host_ethtool_rxnfc_get_set_rxfh(void *dst, + const void *src) +{ + static const argtype ethtool_rx_flow_spec_argtype[] =3D { + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), TYPE_NULL }; + struct ethtool_rxnfc *host =3D dst; + const struct ethtool_rxnfc *target =3D src; + + host->cmd =3D tswap32(target->cmd); + host->flow_type =3D tswap32(target->flow_type); + host->data =3D tswap64(target->data); + + if (host->cmd =3D=3D ETHTOOL_SRXFH) { + /* + * struct ethtool_rxnfc was originally defined for ETHTOOL_{G,S}RX= FH + * with only the cmd, flow_type and data members. Guest program mi= ght + * still be using that definition. + */ + return; + } + if (host->cmd !=3D ETHTOOL_GRXFH) { + fprintf(stderr, "target_to_host_ethtool_rxnfc_get_set_rxfh called = with " + "command 0x%x which is not ETHTOOL_SRXFH or ETHTOOL_GRXFH\= n", + host->cmd); + } + if ((host->flow_type & FLOW_RSS) =3D=3D 0) { + return; + } + /* + * If `FLOW_RSS` was requested then guest program must be using the new + * definition. + */ + thunk_convert(&host->fs, &target->fs, ethtool_rx_flow_spec_argtype, + THUNK_HOST); + host->rule_cnt =3D tswap32(target->rule_cnt); +} + +static int target_ethtool_rxnfc_get_set_rxfh_size(const void *src) +{ + const struct ethtool_rxnfc *target =3D src; + int cmd =3D tswap32(target->cmd); + if (cmd =3D=3D ETHTOOL_SRXFH || + (cmd =3D=3D ETHTOOL_GRXFH && + (tswap32(target->flow_type) & FLOW_RSS) =3D=3D 0)) { + return 16; + } + return sizeof(struct ethtool_rxnfc); +} + +static int host_ethtool_rxnfc_get_set_rxfh_size(const void *src) +{ + const struct ethtool_rxnfc *host =3D src; + if (host->cmd =3D=3D ETHTOOL_SRXFH || + (host->cmd =3D=3D ETHTOOL_GRXFH && (host->flow_type & FLOW_RSS) = =3D=3D 0)) { + return 16; + } + return sizeof(struct ethtool_rxnfc); +} + +const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def =3D { + .convert =3D { + host_to_target_ethtool_rxnfc_get_set_rxfh, + target_to_host_ethtool_rxnfc_get_set_rxfh }, + .thunk_size =3D { + target_ethtool_rxnfc_get_set_rxfh_size, + host_ethtool_rxnfc_get_set_rxfh_size }, + .size =3D { 16, 16 }, + .align =3D { + __alignof__(struct ethtool_rxnfc), + __alignof__(struct ethtool_rxnfc) }, +}; + +/* + * struct ethtool_sset_info { + * __u32 cmd; + * __u32 reserved; + * __u64 sset_mask; + * __u32 data[0]; + * }; + * + * `sset_mask` is a bitmask of string sets. `data` is the buffer for strin= g set + * sizes, containing number of 1s in `sset_mask`'s binary representation n= umber + * of 4-byte entries. + * + * Since all fields are fixed-width and number of 1s in `sset_mask` does n= ot + * change between architectures, host-to-target and target-to-host are + * identical. + */ +static void convert_ethtool_sset_info(void *dst, const void *src) +{ + int i, set_count; + struct ethtool_sset_info *dst_sset_info =3D dst; + const struct ethtool_sset_info *src_sset_info =3D src; + + dst_sset_info->cmd =3D tswap32(src_sset_info->cmd); + dst_sset_info->sset_mask =3D tswap64(src_sset_info->sset_mask); + + set_count =3D ctpop64(src_sset_info->sset_mask); + for (i =3D 0; i < set_count; ++i) { + dst_sset_info->data[i] =3D tswap32(src_sset_info->data[i]); + } +} + +static int ethtool_sset_info_size(const void *src) +{ + const struct ethtool_sset_info *src_sset_info =3D src; + return sizeof(struct ethtool_sset_info) + + ctpop64(src_sset_info->sset_mask) * sizeof(src_sset_info->data[0]); +} + +const StructEntry struct_ethtool_sset_info_def =3D { + .convert =3D { + convert_ethtool_sset_info, convert_ethtool_sset_info }, + .thunk_size =3D { + ethtool_sset_info_size, ethtool_sset_info_size }, + .size =3D { + sizeof(struct ethtool_sset_info), + sizeof(struct ethtool_sset_info) }, + .align =3D { + __alignof__(struct ethtool_sset_info), + __alignof__(struct ethtool_sset_info) }, +}; + +/* + * struct ethtool_rxfh { + * __u32 cmd; + * __u32 rss_context; + * __u32 indir_size; + * __u32 key_size; + * __u8 hfunc; + * __u8 rsvd8[3]; + * __u32 rsvd32; + * __u32 rss_config[0]; + * }; + * + * `rss_config`: indirection table of `indir_size` __u32 elements, followe= d by + * hash key of `key_size` bytes. + * + * `indir_size` could be ETH_RXFH_INDIR_NO_CHANGE when `cmd` is ETHTOOL_SR= SSH + * and there would be no indircetion table in `rss_config`. + */ +static void convert_ethtool_rxfh_header(void *dst, const void *src) +{ + struct ethtool_rxfh *dst_rxfh =3D dst; + const struct ethtool_rxfh *src_rxfh =3D src; + + dst_rxfh->cmd =3D tswap32(src_rxfh->cmd); + dst_rxfh->rss_context =3D tswap32(src_rxfh->rss_context); + dst_rxfh->indir_size =3D tswap32(src_rxfh->indir_size); + dst_rxfh->key_size =3D tswap32(src_rxfh->key_size); + dst_rxfh->hfunc =3D src_rxfh->hfunc; + dst_rxfh->rsvd8[0] =3D src_rxfh->rsvd8[0]; + dst_rxfh->rsvd8[1] =3D src_rxfh->rsvd8[1]; + dst_rxfh->rsvd8[2] =3D src_rxfh->rsvd8[2]; + dst_rxfh->rsvd32 =3D tswap32(src_rxfh->rsvd32); +} + +static void convert_ethtool_rxfh_rss_config( + void *dst, const void *src, uint32_t indir_size, uint32_t key_size) { + uint32_t *dst_rss_config =3D (uint32_t *)dst; + const uint32_t *src_rss_config =3D (const uint32_t *)src; + int i; + for (i =3D 0; i < indir_size; ++i) { + dst_rss_config[i] =3D tswap32(src_rss_config[i]); + } + if (key_size > 0) { + memcpy(dst_rss_config + indir_size, + src_rss_config + indir_size, + key_size); + } +} + +static void host_to_target_ethtool_rxfh(void *dst, const void *src) +{ + struct ethtool_rxfh *target =3D dst; + const struct ethtool_rxfh *host =3D src; + + convert_ethtool_rxfh_header(dst, src); + + const uint32_t indir_size =3D + host->cmd =3D=3D ETHTOOL_SRSSH && + host->indir_size =3D=3D ETH_RXFH_INDIR_NO_CHANGE ? + 0 : + host->indir_size; + convert_ethtool_rxfh_rss_config(target->rss_config, host->rss_config, + indir_size, host->key_size); +} + +static void target_to_host_ethtool_rxfh(void *dst, const void *src) +{ + struct ethtool_rxfh *host =3D dst; + const struct ethtool_rxfh *target =3D src; + + convert_ethtool_rxfh_header(dst, src); + + const uint32_t indir_size =3D + host->cmd =3D=3D ETHTOOL_SRSSH && + host->indir_size =3D=3D ETH_RXFH_INDIR_NO_CHANGE ? + 0 : + host->indir_size; + convert_ethtool_rxfh_rss_config(host->rss_config, target->rss_config, + indir_size, host->key_size); +} + +static int target_ethtool_rxfh_size(const void *src) +{ + const struct ethtool_rxfh *target =3D src; + if (tswap32(target->cmd) =3D=3D ETHTOOL_SRSSH && + tswap32(target->indir_size) =3D=3D ETH_RXFH_INDIR_NO_CHANGE) { + return sizeof(struct ethtool_rxfh) + tswap32(target->key_size); + } + return sizeof(struct ethtool_rxfh) + + tswap32(target->indir_size) * sizeof(target->rss_config[0]) + + tswap32(target->key_size); +} + +static int host_ethtool_rxfh_size(const void *src) +{ + const struct ethtool_rxfh *host =3D src; + if (host->cmd =3D=3D ETHTOOL_SRSSH && + host->indir_size =3D=3D ETH_RXFH_INDIR_NO_CHANGE) { + return sizeof(struct ethtool_rxfh) + host->key_size; + } + return sizeof(struct ethtool_rxfh) + + host->indir_size * sizeof(host->rss_config[0]) + + host->key_size; +} + +const StructEntry struct_ethtool_rxfh_def =3D { + .convert =3D { + host_to_target_ethtool_rxfh, target_to_host_ethtool_rxfh }, + .thunk_size =3D { + target_ethtool_rxfh_size, host_ethtool_rxfh_size }, + .size =3D { + sizeof(struct ethtool_rxfh), sizeof(struct ethtool_rxfh) }, + .align =3D { + __alignof__(struct ethtool_rxfh), __alignof__(struct ethtool_rxfh)= }, +}; + +/* + * struct ethtool_link_settings { + * __u32 cmd; + * __u32 speed; + * __u8 duplex; + * __u8 port; + * __u8 phy_address; + * __u8 autoneg; + * __u8 mdio_support; + * __u8 eth_tp_mdix; + * __u8 eth_tp_mdix_ctrl; + * __s8 link_mode_masks_nwords; + * __u8 transceiver; + * __u8 reserved1[3]; + * __u32 reserved[7]; + * __u32 link_mode_masks[0]; + * }; + * + * layout of link_mode_masks fields: + * __u32 map_supported[link_mode_masks_nwords]; + * __u32 map_advertising[link_mode_masks_nwords]; + * __u32 map_lp_advertising[link_mode_masks_nwords]; + * + * `link_mode_masks_nwords` can be negative when returning from kernel if = the + * provided request size is not supported. + */ + +static void host_to_target_ethtool_link_settings(void *dst, const void *sr= c) +{ + int i; + struct ethtool_link_settings *target =3D dst; + const struct ethtool_link_settings *host =3D src; + + target->cmd =3D tswap32(host->cmd); + target->speed =3D tswap32(host->speed); + target->duplex =3D host->duplex; + target->port =3D host->port; + target->phy_address =3D host->phy_address; + target->autoneg =3D host->autoneg; + target->mdio_support =3D host->mdio_support; + target->eth_tp_mdix =3D host->eth_tp_mdix; + target->eth_tp_mdix_ctrl =3D host->eth_tp_mdix_ctrl; + target->link_mode_masks_nwords =3D host->link_mode_masks_nwords; + target->transceiver =3D host->transceiver; + for (i =3D 0; i < 3; ++i) { + target->reserved1[i] =3D host->reserved1[i]; + } + for (i =3D 0; i < 7; ++i) { + target->reserved[i] =3D tswap32(host->reserved[i]); + } + + if (host->link_mode_masks_nwords > 0) { + for (i =3D 0; i < host->link_mode_masks_nwords * 3; ++i) { + target->link_mode_masks[i] =3D tswap32(host->link_mode_masks[i= ]); + } + } +} + +static void target_to_host_ethtool_link_settings(void *dst, const void *sr= c) +{ + int i; + struct ethtool_link_settings *host =3D dst; + const struct ethtool_link_settings *target =3D src; + + host->cmd =3D tswap32(target->cmd); + host->speed =3D tswap32(target->speed); + host->duplex =3D target->duplex; + host->port =3D target->port; + host->phy_address =3D target->phy_address; + host->autoneg =3D target->autoneg; + host->mdio_support =3D target->mdio_support; + host->eth_tp_mdix =3D target->eth_tp_mdix; + host->eth_tp_mdix_ctrl =3D target->eth_tp_mdix_ctrl; + host->link_mode_masks_nwords =3D target->link_mode_masks_nwords; + host->transceiver =3D target->transceiver; + for (i =3D 0; i < 3; ++i) { + host->reserved1[i] =3D target->reserved1[i]; + } + for (i =3D 0; i < 7; ++i) { + host->reserved[i] =3D tswap32(target->reserved[i]); + } + + if (host->link_mode_masks_nwords > 0) { + for (i =3D 0; i < host->link_mode_masks_nwords * 3; ++i) { + host->link_mode_masks[i] =3D tswap32(target->link_mode_masks[i= ]); + } + } +} + +static int target_ethtool_link_settings_size(const void *src) +{ + const struct ethtool_link_settings *target =3D src; + if (target->link_mode_masks_nwords > 0) { + return sizeof(struct ethtool_link_settings) + + 3 * target->link_mode_masks_nwords * + sizeof(target->link_mode_masks[0]); + } else { + return sizeof(struct ethtool_link_settings); + } +} + +static int host_ethtool_link_settings_size(const void *src) +{ + const struct ethtool_link_settings *host =3D src; + if (host->link_mode_masks_nwords > 0) { + return sizeof(struct ethtool_link_settings) + + 3 * host->link_mode_masks_nwords * + sizeof(host->link_mode_masks[0]); + } else { + return sizeof(struct ethtool_link_settings); + } +} + +const StructEntry struct_ethtool_link_settings_def =3D { + .convert =3D { + host_to_target_ethtool_link_settings, + target_to_host_ethtool_link_settings + }, + .thunk_size =3D { + target_ethtool_link_settings_size, host_ethtool_link_settings_size= }, + .size =3D { + sizeof(struct ethtool_link_settings), + sizeof(struct ethtool_link_settings) }, + .align =3D { + __alignof__(struct ethtool_link_settings), + __alignof__(struct ethtool_link_settings) }, +}; + +/* + * struct ethtool_per_queue_op { + * __u32 cmd; + * __u32 sub_command; + * __u32 queue_mask[__KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32)]; + * char data[]; + * }; + * + * `queue_mask` are a series of bitmasks of the queues. `data` is a comple= te + * command structure for each of the queues addressed. + * + * When `cmd` is `ETHTOOL_PERQUEUE` and `sub_command` is `ETHTOOL_GCOALESC= E` or + * `ETHTOOL_SCOALESCE`, the command structure is `struct ethtool_coalesce`. + */ +static void host_to_target_ethtool_per_queue_op(void *dst, const void *src) +{ + static const argtype ethtool_coalesce_argtype[] =3D { + MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL }; + int i, queue_count; + struct ethtool_per_queue_op *target =3D dst; + const struct ethtool_per_queue_op *host =3D src; + + target->cmd =3D tswap32(host->cmd); + target->sub_command =3D tswap32(host->sub_command); + + queue_count =3D 0; + for (i =3D 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + target->queue_mask[i] =3D tswap32(host->queue_mask[i]); + queue_count +=3D ctpop32(host->queue_mask[i]); + } + + if (host->cmd !=3D ETHTOOL_PERQUEUE || + (host->sub_command !=3D ETHTOOL_GCOALESCE && + host->sub_command !=3D ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to convert the `data` field " + "(host-to-target)\n", + host->cmd, host->sub_command); + return; + } + + for (i =3D 0; i < queue_count; ++i) { + thunk_convert(target->data + i * sizeof(struct ethtool_coalesce), + host->data + i * sizeof(struct ethtool_coalesce), + ethtool_coalesce_argtype, THUNK_TARGET); + } +} + +static void target_to_host_ethtool_per_queue_op(void *dst, const void *src) +{ + static const argtype ethtool_coalesce_argtype[] =3D { + MK_STRUCT(STRUCT_ethtool_coalesce), TYPE_NULL }; + int i, queue_count; + struct ethtool_per_queue_op *host =3D dst; + const struct ethtool_per_queue_op *target =3D src; + + host->cmd =3D tswap32(target->cmd); + host->sub_command =3D tswap32(target->sub_command); + + queue_count =3D 0; + for (i =3D 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + host->queue_mask[i] =3D tswap32(target->queue_mask[i]); + queue_count +=3D ctpop32(host->queue_mask[i]); + } + + if (host->cmd !=3D ETHTOOL_PERQUEUE || + (host->sub_command !=3D ETHTOOL_GCOALESCE && + host->sub_command !=3D ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to convert the `data` field " + "(target-to-host)\n", + host->cmd, host->sub_command); + return; + } + + for (i =3D 0; i < queue_count; ++i) { + thunk_convert(host->data + i * sizeof(struct ethtool_coalesce), + target->data + i * sizeof(struct ethtool_coalesce), + ethtool_coalesce_argtype, THUNK_HOST); + } +} + +static int target_ethtool_per_queue_op_size(const void *src) +{ + int i, queue_count; + const struct ethtool_per_queue_op *target =3D src; + + if (tswap32(target->cmd) !=3D ETHTOOL_PERQUEUE || + (tswap32(target->sub_command) !=3D ETHTOOL_GCOALESCE && + tswap32(target->sub_command) !=3D ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to compute the size of the " + "`data` field (target)\n", + tswap32(target->cmd), tswap32(target->sub_command)); + return sizeof(struct ethtool_per_queue_op); + } + + queue_count =3D 0; + for (i =3D 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + queue_count +=3D ctpop32(target->queue_mask[i]); + } + return sizeof(struct ethtool_per_queue_op) + + queue_count * sizeof(struct ethtool_coalesce); +} + +static int host_ethtool_per_queue_op_size(const void *src) +{ + int i, queue_count; + const struct ethtool_per_queue_op *host =3D src; + + if (host->cmd !=3D ETHTOOL_PERQUEUE || + (host->sub_command !=3D ETHTOOL_GCOALESCE && + host->sub_command !=3D ETHTOOL_SCOALESCE)) { + fprintf(stderr, + "Unknown command 0x%x sub_command 0x%x for " + "ethtool_per_queue_op, unable to compute the size of the " + "`data` field (host)\n", + host->cmd, host->sub_command); + return sizeof(struct ethtool_per_queue_op); + } + + queue_count =3D 0; + for (i =3D 0; i < __KERNEL_DIV_ROUND_UP(MAX_NUM_QUEUE, 32); ++i) { + queue_count +=3D ctpop32(host->queue_mask[i]); + } + return sizeof(struct ethtool_per_queue_op) + + queue_count * sizeof(struct ethtool_coalesce); +} + +const StructEntry struct_ethtool_per_queue_op_def =3D { + .convert =3D { + host_to_target_ethtool_per_queue_op, + target_to_host_ethtool_per_queue_op + }, + .thunk_size =3D { + target_ethtool_per_queue_op_size, host_ethtool_per_queue_op_size }, + .size =3D { + sizeof(struct ethtool_per_queue_op), + sizeof(struct ethtool_per_queue_op) }, + .align =3D { + __alignof__(struct ethtool_per_queue_op), + __alignof__(struct ethtool_per_queue_op) }, +}; + +#define safe_dev_ethtool(fd, ...) \ + safe_syscall(__NR_ioctl, (fd), SIOCETHTOOL, __VA_ARGS__) + +typedef struct EthtoolEntry EthtoolEntry; + +typedef abi_long do_ethtool_fn(const EthtoolEntry *ee, uint8_t *buf_temp, + int fd, struct ifreq *host_ifreq); + +struct EthtoolEntry { + uint32_t cmd; + int access; + do_ethtool_fn *do_ethtool; + const argtype arg_type[3]; +}; + +#define ETHT_R 0x0001 +#define ETHT_W 0x0002 +#define ETHT_RW (ETHT_R | ETHT_W) + +static do_ethtool_fn do_ethtool_get_rxfh; + +static EthtoolEntry ethtool_entries[] =3D { +#define ETHTOOL(cmd, access, ...) \ + { cmd, access, 0, { __VA_ARGS__ } }, +#define ETHTOOL_SPECIAL(cmd, access, dofn, ...) \ + { cmd, access, dofn, { __VA_ARGS__ } }, +#include "ethtool_entries.h" +#undef ETHTOOL +#undef ETHTOOL_SPECIAL + { 0, 0 }, +}; + +/* + * ETHTOOL_GRSSH has two modes of operations: querying the sizes of the in= dir + * and key as well as actually querying the indir and key. When either + * `indir_size` or `key_size` is zero, the size of the corresponding entry= is + * retrieved and updated into the `ethtool_rxfh` struct. When either of th= em is + * non-zero, the actually indir or key is written to `rss_config`. + * + * This causes a problem for the generic framework which converts between = host + * and target structures without the context. When the convertion function= sees + * an `ethtool_rxfh` struct with non-zero `indir_size` or `key_size`, it h= as to + * assume that there are entries in `rss_config` and needs to convert them. + * Unfortunately, when converting the returned `ethtool_rxfh` struct from = host + * to target after an ETHTOOL_GRSSH call with the first mode, the `indir_s= ize` + * and `key_size` fields are populated but there is no actual data to be + * converted. More importantly, user programs would not have prepared enou= gh + * memory for the convertion to take place safely. + * + * ETHTOOL_GRSSH thus needs a special implementation which is aware of the= two + * modes of operations and converts the structure accordingly. + */ +abi_long do_ethtool_get_rxfh(const EthtoolEntry *ee, uint8_t *buf_temp, + int fd, struct ifreq *host_ifreq) +{ + const argtype *arg_type =3D ee->arg_type; + const abi_long ifreq_data =3D (abi_long)(unsigned long)host_ifreq->ifr= _data; + struct ethtool_rxfh *rxfh =3D (struct ethtool_rxfh *)buf_temp; + uint32_t user_indir_size, user_key_size; + abi_long ret; + void *argptr; + + assert(arg_type[0] =3D=3D TYPE_PTR); + assert(ee->access =3D=3D IOC_RW); + arg_type++; + + /* + * As of Linux kernel v5.8-rc4, ETHTOOL_GRSSH calls never read the + * `rss_config` part. Converting only the "header" part suffices. + */ + argptr =3D lock_user(VERIFY_READ, ifreq_data, sizeof(*rxfh), 1); + if (!argptr) { + return -TARGET_EFAULT; + } + convert_ethtool_rxfh_header(rxfh, argptr); + unlock_user(argptr, ifreq_data, sizeof(*rxfh)); + + if (rxfh->cmd !=3D ETHTOOL_GRSSH) { + return -TARGET_EINVAL; + } + user_indir_size =3D rxfh->indir_size; + user_key_size =3D rxfh->key_size; + + host_ifreq->ifr_data =3D (void *)rxfh; + ret =3D get_errno(safe_dev_ethtool(fd, host_ifreq)); + + /* + * When a user program supplies `indir_size` or `key_size` but does not + * match what the kernel has, the syscall returns EINVAL but the struc= ture + * is already updated. Mimicking it here. + */ + argptr =3D lock_user(VERIFY_WRITE, ifreq_data, sizeof(*rxfh), 0); + if (!argptr) { + return -TARGET_EFAULT; + } + convert_ethtool_rxfh_header(argptr, rxfh); + unlock_user(argptr, ifreq_data, 0); + + if (is_error(ret)) { + return ret; + } + + if (user_indir_size > 0 || user_key_size > 0) { + const int rss_config_size =3D + user_indir_size * sizeof(rxfh->rss_config[0]) + user_key_size; + argptr =3D lock_user(VERIFY_WRITE, ifreq_data + sizeof(*rxfh), + rss_config_size, 0); + if (!argptr) { + return -TARGET_EFAULT; + } + convert_ethtool_rxfh_rss_config(argptr, rxfh->rss_config, + user_indir_size, user_key_size); + unlock_user(argptr, ifreq_data + sizeof(*rxfh), rss_config_size); + } + return ret; +} + +/* + * Calculates the size of the data type represented by `type_ptr` with + * `guest_addr` being the underlying memory. Since `type_ptr` may contain + * flexible arrays, we need access to the underlying memory to determine t= heir + * sizes. + */ +static int thunk_size(abi_long guest_addr, const argtype *type_ptr) +{ + /* + * lock_user based on `thunk_type_size` then call `thunk_type_size_wit= h_src` + * on it. + */ + void *src; + int type_size =3D thunk_type_size(type_ptr, /*is_host=3D*/ 0); + if (!thunk_type_has_flexible_array(type_ptr)) { + return type_size; + } + + src =3D lock_user(VERIFY_READ, guest_addr, type_size, 0); + type_size =3D thunk_type_size_with_src(src, type_ptr, /*is_host=3D*/ 0= ); + unlock_user(src, guest_addr, 0); + + return type_size; +} + +abi_long dev_ethtool(int fd, uint8_t *buf_temp) +{ + uint32_t *cmd; + uint32_t host_cmd; + const EthtoolEntry *ee; + const argtype *arg_type; + abi_long ret; + int target_size; + void *argptr; + + /* + * Make a copy of `host_ifreq` because we are going to reuse `buf_temp= ` and + * overwrite it. Further, we will overwrite `host_ifreq.ifreq_data`, so + * keep a copy in `ifreq_data`. + */ + struct ifreq host_ifreq =3D *(struct ifreq *)(unsigned long)buf_temp; + const abi_long ifreq_data =3D (abi_long)(unsigned long)host_ifreq.ifr_= data; + + cmd =3D (uint32_t *)lock_user(VERIFY_READ, ifreq_data, sizeof(uint32_t= ), 0); + host_cmd =3D tswap32(*cmd); + unlock_user(cmd, ifreq_data, 0); + + ee =3D ethtool_entries; + for (;;) { + if (ee->cmd =3D=3D 0) { + qemu_log_mask(LOG_UNIMP, "Unsupported ethtool cmd=3D0x%04lx\n", + (long)host_cmd); + return -TARGET_ENOSYS; + } + if (ee->cmd =3D=3D host_cmd) { + break; + } + ee++; + } + if (ee->do_ethtool) { + return ee->do_ethtool(ee, buf_temp, fd, &host_ifreq); + } + + host_ifreq.ifr_data =3D buf_temp; + /* Even for ETHT_R, cmd still needs to be copied. */ + *(uint32_t *)buf_temp =3D host_cmd; + + arg_type =3D ee->arg_type; + switch (arg_type[0]) { + case TYPE_NULL: + /* no argument other than cmd */ + ret =3D get_errno(safe_dev_ethtool(fd, &host_ifreq)); + break; + case TYPE_PTR: + arg_type++; + target_size =3D thunk_size(ifreq_data, arg_type); + switch (ee->access) { + case ETHT_R: + ret =3D get_errno(safe_dev_ethtool(fd, &host_ifreq)); + if (!is_error(ret)) { + argptr =3D lock_user(VERIFY_WRITE, ifreq_data, target_size= , 0); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); + unlock_user(argptr, ifreq_data, target_size); + } + break; + case ETHT_W: + argptr =3D lock_user(VERIFY_READ, ifreq_data, target_size, 1); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, ifreq_data, 0); + ret =3D get_errno(safe_dev_ethtool(fd, &host_ifreq)); + break; + default: + case ETHT_RW: + argptr =3D lock_user(VERIFY_READ, ifreq_data, target_size, 1); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, ifreq_data, 0); + ret =3D get_errno(safe_dev_ethtool(fd, &host_ifreq)); + if (!is_error(ret)) { + argptr =3D lock_user(VERIFY_WRITE, ifreq_data, target_size= , 0); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); + unlock_user(argptr, ifreq_data, target_size); + } + break; + } + break; + default: + qemu_log_mask(LOG_UNIMP, + "Unsupported ethtool type: cmd=3D0x%04lx type=3D%d\n= ", + (long)host_cmd, arg_type[0]); + ret =3D -TARGET_ENOSYS; + break; + } + return ret; +} diff --git a/linux-user/ethtool.h b/linux-user/ethtool.h new file mode 100644 index 0000000000..6942aef095 --- /dev/null +++ b/linux-user/ethtool.h @@ -0,0 +1,20 @@ +#ifndef ETHTOOL_H +#define ETHTOOL_H + +#include +#include "qemu.h" + +extern const StructEntry struct_ethtool_rxnfc_get_set_rxfh_def; +extern const StructEntry struct_ethtool_sset_info_def; +extern const StructEntry struct_ethtool_rxfh_def; +extern const StructEntry struct_ethtool_link_settings_def; +extern const StructEntry struct_ethtool_per_queue_op_def; + +/* + * Takes the file descriptor and the buffer for temporarily storing data r= ead + * from / to be written to guest memory. `buf_temp` must now contain the h= ost + * representation of `struct ifreq`. + */ +abi_long dev_ethtool(int fd, uint8_t *buf_temp); + +#endif /* ETHTOOL_H */ diff --git a/linux-user/ethtool_entries.h b/linux-user/ethtool_entries.h new file mode 100644 index 0000000000..14f4e80a21 --- /dev/null +++ b/linux-user/ethtool_entries.h @@ -0,0 +1,107 @@ + ETHTOOL(ETHTOOL_GSET, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd))) + ETHTOOL(ETHTOOL_SSET, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_cmd))) + ETHTOOL(ETHTOOL_GDRVINFO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_drvinf= o))) + ETHTOOL(ETHTOOL_GREGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_regs))) + ETHTOOL(ETHTOOL_GWOL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo))) + ETHTOOL(ETHTOOL_SWOL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_wolinfo))) + ETHTOOL(ETHTOOL_GMSGLVL, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SMSGLVL, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GEEE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee))) + ETHTOOL(ETHTOOL_SEEE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_eee))) + ETHTOOL(ETHTOOL_NWAY_RST, 0, TYPE_NULL) + ETHTOOL(ETHTOOL_GLINK, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GEEPROM, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom= ))) + ETHTOOL(ETHTOOL_SEEPROM, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom= ))) + ETHTOOL(ETHTOOL_GCOALESCE, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_coale= sce))) + ETHTOOL(ETHTOOL_SCOALESCE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_coale= sce))) + ETHTOOL(ETHTOOL_GRINGPARAM, ETHT_R, + MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam))) + ETHTOOL(ETHTOOL_SRINGPARAM, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_ringparam))) + ETHTOOL(ETHTOOL_GPAUSEPARAM, ETHT_R, + MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam))) + ETHTOOL(ETHTOOL_SPAUSEPARAM, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_pauseparam))) + ETHTOOL(ETHTOOL_TEST, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_test))) + ETHTOOL(ETHTOOL_GSTRINGS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_gstri= ngs))) + ETHTOOL(ETHTOOL_PHYS_ID, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GSTATS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_stats))) + ETHTOOL(ETHTOOL_GPERMADDR, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_perm_addr))) + ETHTOOL(ETHTOOL_GFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GPFLAGS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SPFLAGS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GRXFH, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh))) + ETHTOOL(ETHTOOL_GRXRINGS, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_GRXCLSRLCNT, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_cnt))) + ETHTOOL(ETHTOOL_GRXCLSRULE, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_GRXCLSRLALL, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rule_locs))) + ETHTOOL(ETHTOOL_SRXFH, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_get_set_rxfh))) + ETHTOOL(ETHTOOL_SRXCLSRLDEL, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_SRXCLSRLINS, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxnfc_rss_context))) + ETHTOOL(ETHTOOL_FLASHDEV, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_flash)= )) + ETHTOOL(ETHTOOL_RESET, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GSSET_INFO, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_sset_info))) + ETHTOOL(ETHTOOL_GRXFHINDIR, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir))) + ETHTOOL(ETHTOOL_SRXFHINDIR, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh_indir))) + ETHTOOL_SPECIAL(ETHTOOL_GRSSH, ETHT_RW, do_ethtool_get_rxfh, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh))) + ETHTOOL(ETHTOOL_SRSSH, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_rxfh))) + ETHTOOL(ETHTOOL_GFEATURES, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_gfeatures))) + ETHTOOL(ETHTOOL_SFEATURES, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_sfeatures))) + ETHTOOL(ETHTOOL_GTXCSUM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GRXCSUM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GSG, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GTSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GGSO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GGRO, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_STXCSUM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SRXCSUM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SSG, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_STSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SGSO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_SGRO, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_value))) + ETHTOOL(ETHTOOL_GCHANNELS, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_chann= els))) + ETHTOOL(ETHTOOL_SCHANNELS, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_chann= els))) + ETHTOOL(ETHTOOL_SET_DUMP, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data))) + ETHTOOL(ETHTOOL_GET_DUMP_FLAG, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump_no_data))) + ETHTOOL(ETHTOOL_GET_DUMP_DATA, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_dump))) + ETHTOOL(ETHTOOL_GET_TS_INFO, ETHT_R, + MK_PTR(MK_STRUCT(STRUCT_ethtool_ts_info))) + ETHTOOL(ETHTOOL_GMODULEINFO, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_modinfo))) + ETHTOOL(ETHTOOL_GMODULEEEPROM, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_eeprom))) + ETHTOOL(ETHTOOL_GTUNABLE, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_tunab= le))) + ETHTOOL(ETHTOOL_STUNABLE, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_tunabl= e))) + ETHTOOL(ETHTOOL_GPHYSTATS, ETHT_RW, MK_PTR(MK_STRUCT(STRUCT_ethtool_stat= s))) + ETHTOOL(ETHTOOL_PERQUEUE, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_per_queue_op))) + ETHTOOL(ETHTOOL_GLINKSETTINGS, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings))) + ETHTOOL(ETHTOOL_SLINKSETTINGS, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_link_settings))) + ETHTOOL(ETHTOOL_PHY_GTUNABLE, ETHT_RW, + MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) + ETHTOOL(ETHTOOL_PHY_STUNABLE, ETHT_W, + MK_PTR(MK_STRUCT(STRUCT_ethtool_tunable))) + ETHTOOL(ETHTOOL_GFECPARAM, ETHT_R, MK_PTR(MK_STRUCT(STRUCT_ethtool_fecpa= ram))) + ETHTOOL(ETHTOOL_GFECPARAM, ETHT_W, MK_PTR(MK_STRUCT(STRUCT_ethtool_fecpa= ram))) diff --git a/linux-user/ioctls.h b/linux-user/ioctls.h index 8efb4d38c0..8bc63b29ed 100644 --- a/linux-user/ioctls.h +++ b/linux-user/ioctls.h @@ -362,6 +362,8 @@ IOCTL(SIOCSIFHWADDR, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) IOCTL(SIOCGIFTXQLEN, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifr= eq))) IOCTL(SIOCSIFTXQLEN, IOC_W, MK_PTR(MK_STRUCT(STRUCT_sockaddr_ifreq))) + IOCTL_SPECIAL(SIOCETHTOOL, IOC_W | IOC_R, do_ioctl_ethtool, + MK_PTR(MK_STRUCT(STRUCT_ptr_ifreq))) IOCTL(SIOCGIFMETRIC, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) IOCTL(SIOCSIFMETRIC, IOC_W, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) IOCTL(SIOCGIFMTU, IOC_W | IOC_R, MK_PTR(MK_STRUCT(STRUCT_int_ifreq))) diff --git a/linux-user/meson.build b/linux-user/meson.build index 2b94e4ba24..1c62f304cd 100644 --- a/linux-user/meson.build +++ b/linux-user/meson.build @@ -1,5 +1,6 @@ linux_user_ss.add(files( 'elfload.c', + 'ethtool.c', 'exit.c', 'fd-trans.c', 'linuxload.c', diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 534753ca12..754d58965b 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -235,6 +235,7 @@ abi_long do_syscall(void *cpu_env, int num, abi_long ar= g1, abi_long arg2, abi_long arg3, abi_long arg4, abi_long arg5, abi_long arg6, abi_long arg7, abi_long arg8); +abi_long get_errno(abi_long ret); extern __thread CPUState *thread_cpu; void cpu_loop(CPUArchState *env); const char *target_strerror(int err); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 7bf99beb18..50ad08d9e7 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -129,6 +129,7 @@ #include "qapi/error.h" #include "fd-trans.h" #include "tcg/tcg.h" +#include "ethtool.h" =20 #ifndef CLONE_IO #define CLONE_IO 0x80000000 /* Clone io context */ @@ -646,7 +647,7 @@ static inline int target_to_host_errno(int err) return err; } =20 -static inline abi_long get_errno(abi_long ret) +abi_long get_errno(abi_long ret) { if (ret =3D=3D -1) return -host_to_target_errno(errno); @@ -4778,16 +4779,6 @@ static abi_long do_ipc(CPUArchState *cpu_env, #endif =20 /* kernel structure types definitions */ - -#define STRUCT(name, ...) STRUCT_ ## name, -#define STRUCT_SPECIAL(name) STRUCT_ ## name, -enum { -#include "syscall_types.h" -STRUCT_MAX -}; -#undef STRUCT -#undef STRUCT_SPECIAL - #define STRUCT(name, ...) static const argtype struct_ ## name ## _def[] = =3D { __VA_ARGS__, TYPE_NULL }; #define STRUCT_SPECIAL(name) #include "syscall_types.h" @@ -4885,6 +4876,29 @@ static abi_long do_ioctl_fs_ioc_fiemap(const IOCTLEn= try *ie, uint8_t *buf_temp, } #endif =20 +static abi_long do_ioctl_ethtool(const IOCTLEntry *ie, uint8_t *buf_temp, + int fd, int cmd, abi_long arg) +{ + const argtype *arg_type =3D ie->arg_type; + int target_size; + void *argptr; + + assert(arg_type[0] =3D=3D TYPE_PTR); + assert(ie->access =3D=3D IOC_RW); + + arg_type++; + target_size =3D thunk_type_size(arg_type, 0); + + argptr =3D lock_user(VERIFY_READ, arg, target_size, 1); + if (!argptr) { + return -TARGET_EFAULT; + } + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, arg, target_size); + + return dev_ethtool(fd, buf_temp); +} + static abi_long do_ioctl_ifconf(const IOCTLEntry *ie, uint8_t *buf_temp, int fd, int cmd, abi_long arg) { diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h index b934d0b606..ae13f11f6c 100644 --- a/linux-user/syscall_defs.h +++ b/linux-user/syscall_defs.h @@ -904,6 +904,8 @@ struct target_rtc_pll_info { #define TARGET_SIOCGIFTXQLEN 0x8942 /* Get the tx queue length = */ #define TARGET_SIOCSIFTXQLEN 0x8943 /* Set the tx queue length = */ =20 +#define TARGET_SIOCETHTOOL 0x8946 /* Ethtool interface = */ + /* ARP cache control calls. */ #define TARGET_OLD_SIOCDARP 0x8950 /* old delete ARP table ent= ry */ #define TARGET_OLD_SIOCGARP 0x8951 /* old get ARP table entry = */ @@ -2863,4 +2865,14 @@ struct target_statx { /* 0x100 */ }; =20 +/* kernel structure types definitions */ +#define STRUCT(name, ...) STRUCT_ ## name, +#define STRUCT_SPECIAL(name) STRUCT_ ## name, +enum { +#include "syscall_types.h" +STRUCT_MAX +}; +#undef STRUCT +#undef STRUCT_SPECIAL + #endif diff --git a/linux-user/syscall_types.h b/linux-user/syscall_types.h index ba2c1518eb..f1d374d0e0 100644 --- a/linux-user/syscall_types.h +++ b/linux-user/syscall_types.h @@ -1,3 +1,4 @@ + STRUCT_SPECIAL(termios) =20 STRUCT(winsize, @@ -631,3 +632,282 @@ STRUCT(usbdevfs_disconnect_claim, TYPE_INT, /* flags */ MK_ARRAY(TYPE_CHAR, USBDEVFS_MAXDRIVERNAME + 1)) /* driver */ #endif /* CONFIG_USBFS */ + +/* ethtool ioctls */ +STRUCT(ethtool_cmd, + TYPE_INT, /* cmd */ + TYPE_INT, /* supported */ + TYPE_INT, /* advertising */ + TYPE_SHORT, /* speed */ + TYPE_CHAR, /* duplex */ + TYPE_CHAR, /* port */ + TYPE_CHAR, /* phy_address */ + TYPE_CHAR, /* transceiver */ + TYPE_CHAR, /* autoneg */ + TYPE_CHAR, /* mdio_support */ + TYPE_INT, /* maxtxpkt */ + TYPE_INT, /* maxrxpkt */ + TYPE_SHORT, /* speed_hi */ + TYPE_CHAR, /* eth_tp_mdix */ + TYPE_CHAR, /* eth_tp_mdix_ctrl */ + TYPE_INT, /* lp_advertising */ + MK_ARRAY(TYPE_INT, 2)) /* reserved */ + +STRUCT(ethtool_drvinfo, + TYPE_INT, /* cmd */ + MK_ARRAY(TYPE_CHAR, 32), /* driver */ + MK_ARRAY(TYPE_CHAR, 32), /* version */ + MK_ARRAY(TYPE_CHAR, 32), /* fw_version[ETHTOOL_FWVERS_LEN] */ + MK_ARRAY(TYPE_CHAR, 32), /* bus_info[ETHTOOL_BUSINFO_LEN] */ + MK_ARRAY(TYPE_CHAR, 32), /* erom_version[ETHTOOL_EROMVERS_LEN] */ + MK_ARRAY(TYPE_CHAR, 12), /* reserved2 */ + TYPE_INT, /* n_priv_flags */ + TYPE_INT, /* n_stats */ + TYPE_INT, /* testinfo_len */ + TYPE_INT, /* eedump_len */ + TYPE_INT) /* regdump_len */ + +STRUCT(ethtool_regs, + TYPE_INT, /* cmd */ + TYPE_INT, /* version */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 2)) /* data[0]: len */ + +STRUCT(ethtool_wolinfo, + TYPE_INT, /* cmd */ + TYPE_INT, /* supported */ + TYPE_INT, /* wolopts */ + MK_ARRAY(TYPE_CHAR, 6)) /* sopass[SOPASS_MAX] */ + +STRUCT(ethtool_value, + TYPE_INT, /* cmd */ + TYPE_INT) /* data */ + +STRUCT(ethtool_eee, + TYPE_INT, /* cmd */ + TYPE_INT, /* supported */ + TYPE_INT, /* advertised */ + TYPE_INT, /* lp_advertised */ + TYPE_INT, /* eee_active */ + TYPE_INT, /* eee_enabled */ + TYPE_INT, /* tx_lpi_enabled */ + TYPE_INT, /* tx_lpi_timer */ + MK_ARRAY(TYPE_INT, 2)) /* reserved */ + +STRUCT(ethtool_eeprom, + TYPE_INT, /* cmd */ + TYPE_INT, /* magic */ + TYPE_INT, /* offset */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */ + +STRUCT(ethtool_coalesce, + TYPE_INT, /* cmd */ + TYPE_INT, /* rx_coalesce_usecs */ + TYPE_INT, /* rx_max_coalesced_frames */ + TYPE_INT, /* rx_coalesce_usecs_irq */ + TYPE_INT, /* rx_max_coalesced_frames_irq */ + TYPE_INT, /* tx_coalesce_usecs */ + TYPE_INT, /* tx_max_coalesced_frames */ + TYPE_INT, /* tx_coalesce_usecs_irq */ + TYPE_INT, /* tx_max_coalesced_frames_irq */ + TYPE_INT, /* stats_block_coalesce_usecs */ + TYPE_INT, /* use_adaptive_rx_coalesce */ + TYPE_INT, /* use_adaptive_tx_coalesce */ + TYPE_INT, /* pkt_rate_low */ + TYPE_INT, /* rx_coalesce_usecs_low */ + TYPE_INT, /* rx_max_coalesced_frames_low */ + TYPE_INT, /* tx_coalesce_usecs_low */ + TYPE_INT, /* tx_max_coalesced_frames_low */ + TYPE_INT, /* pkt_rate_high */ + TYPE_INT, /* rx_coalesce_usecs_high */ + TYPE_INT, /* rx_max_coalesced_frames_high */ + TYPE_INT, /* tx_coalesce_usecs_high */ + TYPE_INT, /* tx_max_coalesced_frames_high */ + TYPE_INT) /* rate_sample_interval */ + +STRUCT(ethtool_ringparam, + TYPE_INT, /* cmd */ + TYPE_INT, /* rx_max_pending */ + TYPE_INT, /* rx_mini_max_pending */ + TYPE_INT, /* rx_jumbo_max_pending */ + TYPE_INT, /* tx_max_pending */ + TYPE_INT, /* rx_pending */ + TYPE_INT, /* rx_mini_pending */ + TYPE_INT, /* rx_jumbo_pending */ + TYPE_INT) /* tx_pending */ + +STRUCT(ethtool_pauseparam, + TYPE_INT, /* cmd */ + TYPE_INT, /* autoneg */ + TYPE_INT, /* rx_pause */ + TYPE_INT) /* tx_pause */ + +STRUCT(ethtool_test, + TYPE_INT, /* cmd */ + TYPE_INT, /* flags */ + TYPE_INT, /* reserved */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 3)) /* data[0]: len */ + +STRUCT(ethtool_gstrings, + TYPE_INT, /* cmd */ + TYPE_INT, /* string_set */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(MK_ARRAY(TYPE_CHAR, 32), 2)) + /* data[0]: len * ETH_GSTRING_LEN */ + +STRUCT(ethtool_stats, + TYPE_INT, /* cmd */ + TYPE_INT, /* n_stats */ + MK_FLEXIBLE_ARRAY(TYPE_LONGLONG, 1)) /* data[0]: n_stats */ + +STRUCT(ethtool_perm_addr, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 1)) /* data[0]: size */ + +STRUCT(ethtool_flow_ext, + MK_ARRAY(TYPE_CHAR, 2), /* padding */ + MK_ARRAY(TYPE_CHAR, 6), /* h_dest[ETH_ALEN] */ + MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_etype */ + MK_ARRAY(TYPE_CHAR, 2), /* __be16 vlan_tci */ + MK_ARRAY(TYPE_CHAR, 8)) /* __be32 data[2] */ + +/* + * Union ethtool_flow_union contains alternatives that are either struct t= hat + * only uses __be* types or char/__u8, or "__u8 hdata[52]". We can treat i= t as + * byte array in all cases. + */ +STRUCT(ethtool_rx_flow_spec, + TYPE_INT, /* flow_type */ + MK_ARRAY(TYPE_CHAR, 52), /* union ethtool_flow_union h_u= */ + MK_STRUCT(STRUCT_ethtool_flow_ext), /* h_ext */ + MK_ARRAY(TYPE_CHAR, 52), /* union ethtool_flow_union m_u= */ + MK_STRUCT(STRUCT_ethtool_flow_ext), /* m_ext */ + TYPE_LONGLONG, /* ring_cookie */ + TYPE_INT) /* location */ + +STRUCT(ethtool_rxnfc_rss_context, + TYPE_INT, /* cmd */ + TYPE_INT, /* flow_type */ + TYPE_LONGLONG, /* data */ + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ + TYPE_INT) /* rss_context */ + +STRUCT(ethtool_rxnfc_rule_cnt, + TYPE_INT, /* cmd */ + TYPE_INT, /* flow_type */ + TYPE_LONGLONG, /* data */ + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ + TYPE_INT) /* rss_cnt */ + +STRUCT(ethtool_rxnfc_rule_locs, + TYPE_INT, /* cmd */ + TYPE_INT, /* flow_type */ + TYPE_LONGLONG, /* data */ + MK_STRUCT(STRUCT_ethtool_rx_flow_spec), /* fs */ + TYPE_INT, /* rss_cnt */ + MK_FLEXIBLE_ARRAY(TYPE_INT, 4)) /* rule_locs[0]: rss_cnt */ + +/* + * For ETHTOOL_{G,S}RXFH, originally only the first three fields are defin= ed, + * but with certain options, more fields are used. + */ +STRUCT_SPECIAL(ethtool_rxnfc_get_set_rxfh) + +STRUCT(ethtool_flash, + TYPE_INT, /* cmd */ + TYPE_INT, /* region */ + MK_ARRAY(TYPE_CHAR, 128)) /* data[ETHTOOL_FLASH_MAX_FILENAME] */ + +STRUCT_SPECIAL(ethtool_sset_info) + +STRUCT(ethtool_rxfh_indir, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(TYPE_INT, 1)) /* ring_index[0]: size */ + +STRUCT_SPECIAL(ethtool_rxfh) + +STRUCT(ethtool_get_features_block, + TYPE_INT, /* available */ + TYPE_INT, /* requested */ + TYPE_INT, /* active */ + TYPE_INT) /* never_changed */ + +STRUCT(ethtool_gfeatures, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_get_features_block), 1)) + /* features[0]: size */ + +STRUCT(ethtool_set_features_block, + TYPE_INT, /* valid */ + TYPE_INT) /* requested */ + +STRUCT(ethtool_sfeatures, + TYPE_INT, /* cmd */ + TYPE_INT, /* size */ + MK_FLEXIBLE_ARRAY(MK_STRUCT(STRUCT_ethtool_set_features_block), 1)) + /* features[0]: size */ + +STRUCT(ethtool_channels, + TYPE_INT, /* cmd */ + TYPE_INT, /* max_rx */ + TYPE_INT, /* max_tx */ + TYPE_INT, /* max_other */ + TYPE_INT, /* max_combined */ + TYPE_INT, /* rx_count */ + TYPE_INT, /* tx_count */ + TYPE_INT, /* other_count */ + TYPE_INT) /* combined_count */ + +/* + * For ETHTOOL_SET_DUMP and ETHTOOL_GET_DUMP_FLAG, the flexible array `dat= a` is + * not used. + */ +STRUCT(ethtool_dump_no_data, + TYPE_INT, /* cmd */ + TYPE_INT, /* version */ + TYPE_INT, /* flag */ + TYPE_INT) /* len */ + +STRUCT(ethtool_dump, + TYPE_INT, /* cmd */ + TYPE_INT, /* version */ + TYPE_INT, /* flag */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_CHAR, 3)) /* data[0]: len */ + +STRUCT(ethtool_ts_info, + TYPE_INT, /* cmd */ + TYPE_INT, /* so_timestamping */ + TYPE_INT, /* phc_index */ + TYPE_INT, /* tx_types */ + MK_ARRAY(TYPE_INT, 3), /* tx_reserved */ + TYPE_INT, /* rx_filters */ + MK_ARRAY(TYPE_INT, 3)) /* rx_reserved */ + +STRUCT(ethtool_modinfo, + TYPE_INT, /* cmd */ + TYPE_INT, /* type */ + TYPE_INT, /* eeprom_len */ + MK_ARRAY(TYPE_INT, 8)) /* reserved */ + +STRUCT(ethtool_tunable, + TYPE_INT, /* cmd */ + TYPE_INT, /* id */ + TYPE_INT, /* type_id */ + TYPE_INT, /* len */ + MK_FLEXIBLE_ARRAY(TYPE_PTRVOID, 3)) /* data[0]: len */ + +STRUCT_SPECIAL(ethtool_link_settings) + +STRUCT(ethtool_fecparam, + TYPE_INT, /* cmd */ + TYPE_INT, /* active_fec */ + TYPE_INT, /* fec */ + TYPE_INT) /* reserved */ + +STRUCT_SPECIAL(ethtool_per_queue_op) diff --git a/tests/tcg/multiarch/ethtool.c b/tests/tcg/multiarch/ethtool.c new file mode 100644 index 0000000000..dcb10230e0 --- /dev/null +++ b/tests/tcg/multiarch/ethtool.c @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const int number_of_entries_to_print =3D 10; +const uint32_t protected_memory_pattern[] =3D { + 0xdeadc0de, 0xb0bb1e, 0xfacade, 0xfeeb1e }; + +static void fail_with(const char *action, const char *cmd_name, int cmd, + int err) +{ + if (errno =3D=3D EOPNOTSUPP) { + printf("Unsupported operation: %s; errno =3D %d: %s.\n" + "TEST SKIPPED (%s =3D 0x%x).\n", + action, err, strerror(err), cmd_name, cmd); + return; + } + if (err) { + fprintf(stderr, + "Failed to %s (%s =3D 0x%x): errno =3D %d: %s\n", + action, cmd_name, cmd, err, strerror(err)); + } else { + fprintf(stderr, + "Failed to %s (%s =3D 0x%x): no errno\n", + action, cmd_name, cmd); + } + exit(err); +} +#define FAIL(action, cmd) fail_with(action, #cmd, cmd, errno) + +/* + * `calloc_protected` and `protected_memory_changed` can be used to verify= that + * a system call does not write pass intended memory boundary. + * + * `ptr =3D calloc_protected(n)` will allocate extra memory after `n` byte= s and + * populate it with a memory pattern. The first `n` bytes are still guaran= teed + * to be zeroed out like `calloc(1, n)`. `protected_memory_changed(ptr, n)` + * takes the pointer and the original size `n` and checks that the memory + * pattern is intact. + */ +uint8_t *calloc_protected(size_t struct_size) +{ + uint8_t *buf =3D (uint8_t *) calloc( + 1, + struct_size + sizeof(protected_memory_pattern)); + memcpy(buf + struct_size, protected_memory_pattern, + sizeof(protected_memory_pattern)); + return buf; +} + +bool protected_memory_changed(const uint8_t *ptr, size_t struct_size) +{ + return memcmp(ptr + struct_size, protected_memory_pattern, + sizeof(protected_memory_pattern)) !=3D 0; +} + +void print_entries(const char *fmt, int len, uint32_t *entries) +{ + int i; + for (i =3D 0; i < len && i < number_of_entries_to_print; ++i) { + printf(fmt, entries[i]); + } + if (len > number_of_entries_to_print) { + printf(" (%d more omitted)", len - number_of_entries_to_print); + } +} + +void basic_test(int socketfd, struct ifreq ifr) +{ + struct ethtool_drvinfo drvinfo; + drvinfo.cmd =3D ETHTOOL_GDRVINFO; + ifr.ifr_data =3D (void *)&drvinfo; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get driver info", ETHTOOL_GDRVINFO); + return; + } + printf("Driver: %s (version %s)\n", drvinfo.driver, drvinfo.version); +} + +/* Test flexible array. */ +void test_get_stats(int socketfd, struct ifreq ifr, int n_stats) +{ + int i; + struct ethtool_stats *stats =3D (struct ethtool_stats *)calloc( + 1, sizeof(*stats) + sizeof(stats->data[0]) * n_stats); + stats->cmd =3D ETHTOOL_GSTATS; + stats->n_stats =3D n_stats; + ifr.ifr_data =3D (void *)stats; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get statastics", ETHTOOL_GSTATS); + free(stats); + return; + } + if (stats->n_stats !=3D n_stats) { + FAIL("get consistent number of statistics", ETHTOOL_GSTATS); + } + for (i =3D 0; i < stats->n_stats && i < number_of_entries_to_print; ++= i) { + printf("stats[%d] =3D %llu\n", i, (unsigned long long)stats->data[= i]); + } + if (stats->n_stats > number_of_entries_to_print) { + printf("(%d more omitted)\n", + stats->n_stats - number_of_entries_to_print); + } + free(stats); +} + +/* Test flexible array with char array as elements. */ +void test_get_strings(int socketfd, struct ifreq ifr, int n_stats) +{ + int i; + struct ethtool_gstrings *gstrings =3D + (struct ethtool_gstrings *)calloc( + 1, sizeof(*gstrings) + ETH_GSTRING_LEN * n_stats); + gstrings->cmd =3D ETHTOOL_GSTRINGS; + gstrings->string_set =3D ETH_SS_STATS; + gstrings->len =3D n_stats; + ifr.ifr_data =3D (void *)gstrings; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get string set", ETHTOOL_GSTRINGS); + free(gstrings); + return; + } + if (gstrings->len !=3D n_stats) { + FAIL("get consistent number of statistics", ETHTOOL_GSTRINGS); + } + for (i =3D 0; i < gstrings->len && i < number_of_entries_to_print; ++i= ) { + printf("stat_names[%d] =3D %.*s\n", + i, ETH_GSTRING_LEN, gstrings->data + i * ETH_GSTRING_LEN); + } + if (gstrings->len > number_of_entries_to_print) { + printf("(%d more omitted)\n", + gstrings->len - number_of_entries_to_print); + } + free(gstrings); +} + +/* + * Testing manual implementation of converting `struct ethtool_sset_info`,= also + * info for subsequent tests. + */ +int test_get_sset_info(int socketfd, struct ifreq ifr) +{ + const int n_sset =3D 2; + int n_stats; + struct ethtool_sset_info *sset_info =3D + (struct ethtool_sset_info *)calloc( + 1, sizeof(*sset_info) + sizeof(sset_info->data[0]) * n_sset); + sset_info->cmd =3D ETHTOOL_GSSET_INFO; + sset_info->sset_mask =3D 1 << ETH_SS_TEST | 1 << ETH_SS_STATS; + assert(__builtin_popcount(sset_info->sset_mask) =3D=3D n_sset); + ifr.ifr_data =3D (void *)sset_info; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + fail_with("get string set info", "ETHTOOL_GSSET_INFO", + ETHTOOL_GSSET_INFO, errno); + free(sset_info); + return 0; + } + if ((sset_info->sset_mask & (1 << ETH_SS_STATS)) =3D=3D 0) { + puts("No stats string set info, SKIPPING dependent tests"); + free(sset_info); + return 0; + } + n_stats =3D (sset_info->sset_mask & (1 << ETH_SS_TEST)) ? + sset_info->data[1] : + sset_info->data[0]; + printf("n_stats =3D %d\n", n_stats); + free(sset_info); + return n_stats; +} + +/* + * Test manual implementation of converting `struct ethtool_rxnfc`, focusi= ng on + * the case where only the first three fields are present. (The original s= truct + * definition.) + */ +void test_get_rxfh(int socketfd, struct ifreq ifr) +{ + struct ethtool_rxnfc *rxnfc; + const int rxnfc_first_three_field_size =3D + sizeof(rxnfc->cmd) + sizeof(rxnfc->flow_type) + sizeof(rxnfc->data= ); + rxnfc =3D (struct ethtool_rxnfc *)calloc_protected( + rxnfc_first_three_field_size); + rxnfc->cmd =3D ETHTOOL_GRXFH; + rxnfc->flow_type =3D TCP_V4_FLOW; + ifr.ifr_data =3D (void *)rxnfc; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get RX flow classification rules", ETHTOOL_GRXFH); + free(rxnfc); + return; + } + if (protected_memory_changed((const uint8_t *)rxnfc, + rxnfc_first_three_field_size)) { + FAIL("preserve memory after the first three fields", ETHTOOL_GRXFH= ); + } + printf("Flow hash bitmask (flow_type =3D TCP v4): 0x%llx\n", + (unsigned long long)rxnfc->data); + free(rxnfc); +} + +/* Test manual implementation of converting `struct ethtool_link_settings`= . */ +void test_get_link_settings(int socketfd, struct ifreq ifr) +{ + int link_mode_masks_nwords; + struct ethtool_link_settings *link_settings_header =3D + (struct ethtool_link_settings *) calloc_protected( + sizeof(*link_settings_header)); + link_settings_header->cmd =3D ETHTOOL_GLINKSETTINGS; + link_settings_header->link_mode_masks_nwords =3D 0; + ifr.ifr_data =3D (void *)link_settings_header; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get link settings mask sizes", ETHTOOL_GLINKSETTINGS); + free(link_settings_header); + return; + } + if (protected_memory_changed((const uint8_t *)link_settings_header, + sizeof(*link_settings_header))) { + FAIL("preserve link_mode_masks", ETHTOOL_GLINKSETTINGS); + } + if (link_settings_header->link_mode_masks_nwords >=3D 0) { + FAIL("complete handshake", ETHTOOL_GLINKSETTINGS); + } + link_mode_masks_nwords =3D -link_settings_header->link_mode_masks_nwor= ds; + + struct ethtool_link_settings *link_settings =3D + (struct ethtool_link_settings *)calloc( + 1, + sizeof(*link_settings) + + sizeof(link_settings_header->link_mode_masks[0]) * + link_mode_masks_nwords * 3); + link_settings->cmd =3D ETHTOOL_GLINKSETTINGS; + link_settings->link_mode_masks_nwords =3D link_mode_masks_nwords; + ifr.ifr_data =3D (void *)link_settings; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get link settings", ETHTOOL_GLINKSETTINGS); + free(link_settings_header); + free(link_settings); + return; + } + if (link_settings->link_mode_masks_nwords !=3D link_mode_masks_nwords)= { + FAIL("have consistent number of mode masks", ETHTOOL_GLINKSETTINGS= ); + } + + printf("Link speed: %d MB\n", link_settings->speed); + printf("Number of link mode masks: %d\n", + link_settings->link_mode_masks_nwords); + if (link_settings->link_mode_masks_nwords > 0) { + printf("Supported bitmap:"); + print_entries(" 0x%08x", + link_settings->link_mode_masks_nwords, + link_settings->link_mode_masks); + putchar('\n'); + + printf("Advertising bitmap:"); + print_entries(" 0x%08x", + link_settings->link_mode_masks_nwords, + link_settings->link_mode_masks + + link_settings->link_mode_masks_nwords); + putchar('\n'); + + printf("Lp advertising bitmap:"); + print_entries(" 0x%08x", + link_settings->link_mode_masks_nwords, + link_settings->link_mode_masks + + 2 * link_settings->link_mode_masks_nwords); + putchar('\n'); + } + + free(link_settings_header); + free(link_settings); +} + +/* Test manual implementation of converting `struct ethtool_per_queue_op`.= */ +void test_perqueue(int socketfd, struct ifreq ifr) +{ + const int n_queue =3D 2; + int i; + struct ethtool_per_queue_op *per_queue_op =3D + (struct ethtool_per_queue_op *)calloc( + 1, + sizeof(*per_queue_op) + sizeof(struct ethtool_coalesce) * n_qu= eue); + per_queue_op->cmd =3D ETHTOOL_PERQUEUE; + per_queue_op->sub_command =3D ETHTOOL_GCOALESCE; + per_queue_op->queue_mask[0] =3D 0x3; + ifr.ifr_data =3D (void *)per_queue_op; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get coalesce per queue", ETHTOOL_PERQUEUE); + free(per_queue_op); + return; + } + for (i =3D 0; i < n_queue; ++i) { + struct ethtool_coalesce *coalesce =3D (struct ethtool_coalesce *)( + per_queue_op->data + sizeof(*coalesce) * i); + if (coalesce->cmd !=3D ETHTOOL_GCOALESCE) { + fprintf(stderr, + "ETHTOOL_PERQUEUE (%d) sub_command ETHTOOL_GCOALESCE (= %d) " + "fails to set entry %d's cmd to ETHTOOL_GCOALESCE, got= %d " + "instead\n", + ETHTOOL_PERQUEUE, ETHTOOL_GCOALESCE, i, + coalesce->cmd); + exit(-1); + } + printf("rx_coalesce_usecs[%d] =3D %u\nrx_max_coalesced_frames[%d] = =3D %u\n", + i, coalesce->rx_coalesce_usecs, + i, coalesce->rx_max_coalesced_frames); + } + + free(per_queue_op); +} + +/* Test manual implementation of ETHTOOL_GRSSH. */ +void test_get_rssh(int socketfd, struct ifreq ifr) +{ + int i; + struct ethtool_rxfh *rxfh_header =3D + (struct ethtool_rxfh *)calloc_protected(sizeof(*rxfh_header)); + rxfh_header->cmd =3D ETHTOOL_GRSSH; + ifr.ifr_data =3D (void *)rxfh_header; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get RX flow hash indir and hash key size", ETHTOOL_GRSSH); + free(rxfh_header); + return; + } + if (protected_memory_changed((const uint8_t *)rxfh_header, + sizeof(*rxfh_header))) { + FAIL("preserve rss_config", ETHTOOL_GRSSH); + } + printf("RX flow hash indir size =3D %d\nRX flow hash key size =3D %d\n= ", + rxfh_header->indir_size, rxfh_header->key_size); + + struct ethtool_rxfh *rxfh =3D (struct ethtool_rxfh *)calloc( + 1, + sizeof(*rxfh) + 4 * rxfh_header->indir_size + rxfh_header->key_siz= e); + *rxfh =3D *rxfh_header; + ifr.ifr_data =3D (void *)rxfh; + if (ioctl(socketfd, SIOCETHTOOL, &ifr) =3D=3D -1) { + FAIL("get RX flow hash indir and hash key", ETHTOOL_GRSSH); + free(rxfh_header); + free(rxfh); + return; + } + + if (rxfh->indir_size =3D=3D 0) { + printf("No RX flow hash indir\n"); + } else { + printf("RX flow hash indir:"); + print_entries(" 0x%08x", rxfh->indir_size, rxfh->rss_config); + putchar('\n'); + } + + if (rxfh->key_size =3D=3D 0) { + printf("No RX flow hash key\n"); + } else { + char *key =3D (char *)(rxfh->rss_config + rxfh->indir_size); + printf("RX flow hash key:"); + for (i =3D 0; i < rxfh->key_size; ++i) { + if (i % 2 =3D=3D 0) { + putchar(' '); + } + printf("%02hhx", key[i]); + } + putchar('\n'); + } + free(rxfh_header); + free(rxfh); +} + +int main(int argc, char **argv) +{ + int socketfd, n_stats, i; + struct ifreq ifr; + + socketfd =3D socket(AF_INET, SOCK_DGRAM, 0); + if (socketfd =3D=3D -1) { + int err =3D errno; + fprintf(stderr, + "Failed to open socket: errno =3D %d: %s\n", + err, strerror(err)); + return err; + } + + for (i =3D 1;; ++i) { + ifr.ifr_ifindex =3D i; + if (ioctl(socketfd, SIOCGIFNAME, &ifr) =3D=3D -1) { + puts("Could not find a non-loopback interface, SKIPPING"); + return 0; + } + if (strncmp(ifr.ifr_name, "lo", IFNAMSIZ) !=3D 0) { + break; + } + } + printf("Interface index: %d\nInterface name: %.*s\n", + ifr.ifr_ifindex, IFNAMSIZ, ifr.ifr_name); + + basic_test(socketfd, ifr); + + n_stats =3D test_get_sset_info(socketfd, ifr); + if (n_stats > 0) { + /* Testing lexible arrays. */ + test_get_stats(socketfd, ifr, n_stats); + test_get_strings(socketfd, ifr, n_stats); + } + + /* Testing manual implementations of structure convertions. */ + test_get_rxfh(socketfd, ifr); + test_get_link_settings(socketfd, ifr); + test_perqueue(socketfd, ifr); + + /* Testing manual implementations of operations. */ + test_get_rssh(socketfd, ifr); + + return 0; +} --=20 2.29.2.684.gfbc64c5ab5-goog