From 850f26459cdd568db8ee870d41b317f886854750 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 18 Feb 2023 12:04:04 +0000 Subject: [PATCH] drivers: Look up vendor and device names in PCI and USB IDs database (cherry picked from commit ca21ee4918cd7d4472bd875a495752a03a03fa87) --- .gitignore | 1 + common | 2 +- configure.ac | 1 + drivers/Makefile.am | 6 +- drivers/drivers.ml | 31 +++++ drivers/hwdata.ml | 187 +++++++++++++++++++++++++++ drivers/hwdata.mli | 31 +++++ drivers/hwdata_config.ml.in | 26 ++++ drivers/hwdata_config.mli | 35 +++++ drivers/test-virt-drivers-windows.sh | 13 +- m4/guestfs-libraries.m4 | 3 + po/POTFILES-ml | 2 + 12 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 drivers/hwdata.ml create mode 100644 drivers/hwdata.mli create mode 100644 drivers/hwdata_config.ml.in create mode 100644 drivers/hwdata_config.mli diff --git a/.gitignore b/.gitignore index b0ada2e3c..c0ca330a3 100644 --- a/.gitignore +++ b/.gitignore @@ -95,6 +95,7 @@ Makefile.in /customize/virt-customize /df/virt-df /drivers/.depend +/drivers/hwdata_config.ml /drivers/virt-drivers /diff/virt-diff /edit/virt-edit Submodule common 007d0506c..90e0077e2: diff --git a/common/mlstdutils/std_utils.ml b/common/mlstdutils/std_utils.ml index 323bfec..0d2fa22 100644 --- a/common/mlstdutils/std_utils.ml +++ b/common/mlstdutils/std_utils.ml @@ -139,13 +139,19 @@ module String = struct done; if not !r then s else Bytes.to_string b2 + let break n str = + let len = String.length str in + if n < 0 then "", str + else if n >= len then str, "" + else sub str 0 n, sub str n (len-n) + let rec split sep str = - let len = length sep in - let seplen = length str in + let seplen = length sep in + let strlen = length str in let i = find str sep in if i = -1 then str, "" else ( - sub str 0 i, sub str (i + len) (seplen - i - len) + sub str 0 i, sub str (i + seplen) (strlen - i - seplen) ) and nsplit ?(max = 0) sep str = @@ -287,6 +293,25 @@ module List = struct | x :: xs when f x -> x :: takewhile f xs | _ -> [] + let take n xs = + if n <= 0 then [] + else ( + (* This optimisation avoids copying xs. *) + let len = List.length xs in + if len <= n then xs + else ( + let rec take n = function + | x :: xs when n >= 1 -> x :: take (n-1) xs + | _ -> [] + in + take n xs + ) + ) + let rec drop n xs = + if n <= 0 then xs + else if xs = [] then [] + else drop (n-1) (List.tl xs) + let rec filter_map f = function | [] -> [] | x :: xs -> diff --git a/common/mlstdutils/std_utils.mli b/common/mlstdutils/std_utils.mli index d576243..a39ac5f 100644 --- a/common/mlstdutils/std_utils.mli +++ b/common/mlstdutils/std_utils.mli @@ -87,6 +87,10 @@ module String : sig [str] with [s2]. *) val replace_char : string -> char -> char -> string (** Replace character in string. *) + val break : int -> string -> string * string + (** [break n str] breaks a string at the nth byte, returning the + first and second parts. If [n] is beyond the end of the + string it returns [(str, "")]. *) val split : string -> string -> string * string (** [split sep str] splits [str] at the first occurrence of the separator [sep], returning the part before and the part after. @@ -193,6 +197,16 @@ module List : sig For any list [xs] and function [f], [xs = takewhile f xs @ dropwhile f xs] *) + + val take : int -> 'a list -> 'a list + (** [take n xs] returns the first [n] elements of [xs]. If [xs] is + shorter than [n], then it returns [xs]. Note it never fails + for any input. *) + val drop : int -> 'a list -> 'a list + (** [drop n xs] returns the suffix of [xs] after the first [n] + elements. If [xs] is shorter than [n], then it returns the empty + list. Note it never fails for any input. *) + val filter_map : ('a -> 'b option) -> 'a list -> 'b list (** [filter_map f xs] applies [f] to each element of [xs]. If [f x] returns [Some y] then [y] is added to the returned list. *) diff --git a/common/mlstdutils/std_utils_tests.ml b/common/mlstdutils/std_utils_tests.ml index d161b5e..3f5bb1a 100644 --- a/common/mlstdutils/std_utils_tests.ml +++ b/common/mlstdutils/std_utils_tests.ml @@ -85,6 +85,18 @@ let test_string_find ctx = assert_equal_int (-1) (String.find "" "baz"); assert_equal_int (-1) (String.find "foobar" "baz") +(* Test Std_utils.String.break. *) +let test_string_break ctx = + assert_equal_stringpair ("a", "b") (String.break 1 "ab"); + assert_equal_stringpair ("", "ab") (String.break 0 "ab"); + assert_equal_stringpair ("", "ab") (String.break (-1) "ab"); + assert_equal_stringpair ("ab", "") (String.break 2 "ab"); + assert_equal_stringpair ("ab", "") (String.break 3 "ab"); + assert_equal_stringpair ("abc", "def") (String.break 3 "abcdef"); + assert_equal_stringpair ("", "") (String.break 0 ""); + assert_equal_stringpair ("", "") (String.break (-2) ""); + assert_equal_stringpair ("", "") (String.break 2 "") + (* Test Std_utils.String.split. *) let test_string_split ctx = assert_equal_stringpair ("a", "b") (String.split " " "a b"); @@ -169,6 +181,7 @@ let suite = "char.mem" >:: test_char_mem; "strings.is_prefix" >:: test_string_is_prefix; "strings.is_suffix" >:: test_string_is_suffix; + "strings.break" >:: test_string_break; "strings.find" >:: test_string_find; "strings.split" >:: test_string_split; "strings.nsplit" >:: test_string_nsplit; diff --git a/configure.ac b/configure.ac index e7fcff136..059cfbf90 100644 --- a/configure.ac +++ b/configure.ac @@ -138,6 +138,7 @@ AC_CONFIG_FILES([Makefile df/Makefile diff/Makefile drivers/Makefile + drivers/hwdata_config.ml edit/Makefile format/Makefile get-kernel/Makefile diff --git a/drivers/Makefile.am b/drivers/Makefile.am index d27fc2e27..7e0ef659c 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -27,9 +27,13 @@ EXTRA_DIST = \ virt-drivers.pod SOURCES_MLI = \ - drivers.mli + drivers.mli \ + hwdata_config.mli \ + hwdata.mli SOURCES_ML = \ + hwdata_config.ml \ + hwdata.ml \ drivers.ml SOURCES_C = \ diff --git a/drivers/drivers.ml b/drivers/drivers.ml index 57cfb557c..f02165fa4 100644 --- a/drivers/drivers.ml +++ b/drivers/drivers.ml @@ -235,8 +235,14 @@ and windows_hardware_to_xml = function (Option.map (fun v -> ("class", sprintf "%06LX" v)) pci_class); List.may_push_back attrs (Option.map (fun v -> ("vendor", sprintf "%04LX" v)) pci_vendor); + let vendorname = get_pci_vendor pci_vendor in + List.may_push_back attrs + (Option.map (fun v -> "vendorname", v) vendorname); List.may_push_back attrs (Option.map (fun v -> ("device", sprintf "%04LX" v)) pci_device); + let devicename = get_pci_device pci_vendor pci_device in + List.may_push_back attrs + (Option.map (fun v -> "devicename", v) devicename); List.may_push_back attrs (Option.map (fun v -> ("subsystem", sprintf "%08LX" v)) pci_subsys); List.may_push_back attrs @@ -261,8 +267,14 @@ and windows_hardware_to_xml = function let attrs = ref [] in List.may_push_back attrs (Option.map (fun v -> ("vendor", sprintf "%04LX" v)) usb_vendor); + let vendorname = get_usb_vendor usb_vendor in + List.may_push_back attrs + (Option.map (fun v -> "vendorname", v) vendorname); List.may_push_back attrs (Option.map (fun v -> ("product", sprintf "%04LX" v)) usb_product); + let productname = get_usb_device usb_vendor usb_product in + List.may_push_back attrs + (Option.map (fun v -> "productname", v) productname); List.may_push_back attrs (Option.map (fun v -> ("revision", sprintf "%02LX" v)) usb_rev); List.may_push_back attrs @@ -272,6 +284,25 @@ and windows_hardware_to_xml = function | Other path -> Comment (sprintf "unknown DeviceId: %s" (String.concat "\\" path)) +and get_pci_vendor v = get_hwdata'1 Hwdata.pci_vendor v +and get_pci_device v d = get_hwdata'2 Hwdata.pci_device v d +and get_usb_vendor v = get_hwdata'1 Hwdata.usb_vendor v +and get_usb_device v d = get_hwdata'2 Hwdata.usb_device v d + +and get_hwdata'1 f = function + | Some i64 when i64 >= 0_L && i64 <= 0xffff_L -> + let i32 = Int64.to_int32 i64 in + f i32 + | _ -> None + +and get_hwdata'2 f v d = + match v, d with + | Some v64, Some d64 when v64 >= 0_L && v64 <= 0xffff_L && + d64 >= 0_L && d64 <= 0xffff_L -> + let v32 = Int64.to_int32 v64 and d32 = Int64.to_int32 d64 in + f v32 d32 + | _ -> None + (* Main program. *) let main () = let add, ks = parse_cmdline () in diff --git a/drivers/hwdata.ml b/drivers/hwdata.ml new file mode 100644 index 000000000..4b46eff68 --- /dev/null +++ b/drivers/hwdata.ml @@ -0,0 +1,187 @@ +(* virt-drivers + * Copyright (C) 2009-2023 Red Hat Inc. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Std_utils +open Tools_utils +open Common_gettext.Gettext + +open Printf +open Scanf + +module DBKey = struct + type t = + | Vendor of int32 + | Device of int32 * int32 + let compare = compare +end +module DB = Map.Make (DBKey) + +let is_4_digit_hex id = + String.length id = 4 && + Char.isxdigit id.[0] && + Char.isxdigit id.[1] && + Char.isxdigit id.[2] && + Char.isxdigit id.[3] +let hex_to_int32 id = sscanf id "%lx" identity + +(* Loads one of the [*.ids] files, returning the entries as a + * 3 level map. Returns [None] if the file could not be opened + * or parsed. + *) +let load filename = + try + let lines = read_whole_file filename in + let lines = String.lines_split lines in + + (* This loop drops blank lines and comments, splits the fields of + * the database, and returns [(lineno, indent, key, label) list]. + *) + let rec loop lineno acc = function + | [] -> List.rev acc + (* Blank lines. *) + | "" :: lines -> + loop (lineno+1) acc lines + (* Note that # only starts a comment at the beginning of the line. *) + | comment :: lines when String.is_prefix comment "#" -> + loop (lineno+1) acc lines + (* Otherwise its some data. *) + | line :: lines -> + let len = String.length line in + let indent = + let rec counttabs i = + if i < len && line.[i] = '\t' then 1 + counttabs (i+1) else 0 + in + counttabs 0 in + let line = String.sub line indent (len - indent) in + + let n = String.cspan line " \t" in + let key, label = String.break n line in + let n = String.span label " \t" in + let _, label = String.break n label in + + let acc = + if key = "" && label = "" then acc + else (lineno, indent, key, label) :: acc in + + loop (lineno+1) acc lines + in + let lines = loop 1 [] lines in + + (* Since the format is essentially a space-saving one where + * vendor name + * \t device name + * is short for: + * vendor name + * vendor device name + * pull the fields from previous lines down, resulting in + * a flat list. + *) + let rec loop keys acc = function + | [] -> List.rev acc + | (lineno, indent, key, label) :: lines -> + let prefix = List.take indent keys in + let keys = prefix @ [ key ] in + let acc = (lineno, keys, label) :: acc in + loop keys acc lines + in + let lines = loop [] [] lines in + + (* + List.iter ( + fun (lineno, keys, label) -> + eprintf "[%s] -> %s # line %d\n" + (String.concat ";" keys) label lineno + ) lines; + *) + + (* Now we can finally process the database. + * + * We currently ignore the [C] (class) and other records + * that appear at the end of the file. We might want to + * try parsing these in future. It will require changes to + * the code above because the label isn't parsed right. + *) + let db = + List.fold_left ( + fun db (lineno, keys, label) -> + let loc = filename, lineno in + match keys with + | [vendor] when is_4_digit_hex vendor -> + let vendor = hex_to_int32 vendor in + DB.add (Vendor vendor) (label, loc) db + | [vendor; device] when is_4_digit_hex vendor && + is_4_digit_hex device -> + let vendor = hex_to_int32 vendor in + let device = hex_to_int32 device in + DB.add (Device (vendor, device)) (label, loc) db + | _ -> + db + ) DB.empty lines in + + Some db + with exn -> + warning (f_"hwdata: %s: %s") filename (Printexc.to_string exn); + None + +(* Lazily load the PCI database, if present. *) +let pci_db = + let filename = Hwdata_config.pci_ids in + lazy (match filename with None -> None | Some filename -> load filename) + +(* Look up PCI vendor and device ID. *) +let pci_vendor vendor = + let db = Lazy.force pci_db in + match db with + | None -> None + | Some db -> + match DB.find_opt (Vendor vendor) db with + | None -> None + | Some (label, _) -> Some label + +let pci_device vendor device = + let db = Lazy.force pci_db in + match db with + | None -> None + | Some db -> + match DB.find_opt (Device (vendor, device)) db with + | None -> None + | Some (label, _) -> Some label + +(* Lazily load the USB database, if present. *) +let usb_db = + let filename = Hwdata_config.usb_ids in + lazy (match filename with None -> None | Some filename -> load filename) + +(* Look up USB vendor and device ID. *) +let usb_vendor vendor = + let db = Lazy.force usb_db in + match db with + | None -> None + | Some db -> + match DB.find_opt (Vendor vendor) db with + | None -> None + | Some (label, _) -> Some label + +let usb_device vendor device = + let db = Lazy.force usb_db in + match db with + | None -> None + | Some db -> + match DB.find_opt (Device (vendor, device)) db with + | None -> None + | Some (label, _) -> Some label diff --git a/drivers/hwdata.mli b/drivers/hwdata.mli new file mode 100644 index 000000000..972dfe1f6 --- /dev/null +++ b/drivers/hwdata.mli @@ -0,0 +1,31 @@ +(* virt-drivers + * Copyright (C) 2013-2023 Red Hat Inc. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +(** Look up PCI and USB vendor and device IDs. *) + +val pci_vendor : int32 -> string option +(** Look up the PCI vendor ID. If found, return the name. *) + +val pci_device : int32 -> int32 -> string option +(** Look up the PCI vendor & device ID. If found, return the name. *) + +val usb_vendor : int32 -> string option +(** Look up the USB vendor ID. If found, return the name. *) + +val usb_device : int32 -> int32 -> string option +(** Look up the USB vendor & device ID. If found, return the name. *) diff --git a/drivers/hwdata_config.ml.in b/drivers/hwdata_config.ml.in new file mode 100644 index 000000000..fa792c086 --- /dev/null +++ b/drivers/hwdata_config.ml.in @@ -0,0 +1,26 @@ +(* virt-drivers + * @configure_input@ + * Copyright (C) 2009-2023 Red Hat Inc. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +open Std_utils + +let dir = "@HWDATA_PKGDATADIR@" +let dir = if dir = "" then None else Some dir + +let pci_ids = Option.map (fun d -> d // "pci.ids") dir +let usb_ids = Option.map (fun d -> d // "usb.ids") dir diff --git a/drivers/hwdata_config.mli b/drivers/hwdata_config.mli new file mode 100644 index 000000000..877e9e28a --- /dev/null +++ b/drivers/hwdata_config.mli @@ -0,0 +1,35 @@ +(* virt-drivers + * Copyright (C) 2013-2023 Red Hat Inc. + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) + +val dir : string option +(** [pkgdatadir] variable defined by hwdata.pc + + This is the name of the directory containing [pci.ids] and + related files which contain the PCI IDs. *) + +val pci_ids : string option +(** Path to the [pci.ids] file. + + Note at runtime this is an optional dependency, so it may + not at exist even if not [None]. *) + +val usb_ids : string option +(** Path to the [usb.ids] file. + + Note at runtime this is an optional dependency, so it may + not at exist even if not [None]. *) diff --git a/drivers/test-virt-drivers-windows.sh b/drivers/test-virt-drivers-windows.sh index df3f36c64..4131f6e5e 100755 --- a/drivers/test-virt-drivers-windows.sh +++ b/drivers/test-virt-drivers-windows.sh @@ -22,9 +22,18 @@ $TEST_FUNCTIONS skip_if_skipped skip_unless_phony_guest windows.img -rm -f actual-windows.xml +rm -f actual-windows.xml actual-windows.xml.bak $VG virt-drivers --format=raw -a ../test-data/phony-guests/windows.img > actual-windows.xml + +# We can't predict if hwdata is available, so we don't know if +# vendorname and devicename fields will be present. If present, +# remove them before comparison. +mv actual-windows.xml actual-windows.xml.bak +sed -e "s/ vendorname='\([^']*\)'//g" \ + -e "s/ devicename='\([^']*\)'//g" \ + < actual-windows.xml.bak > actual-windows.xml + diff -ur -I "generated by" expected-windows.xml actual-windows.xml -rm actual-windows.xml +rm actual-windows.xml actual-windows.xml.bak diff --git a/m4/guestfs-libraries.m4 b/m4/guestfs-libraries.m4 index 2d252bf9e..32f93afda 100644 --- a/m4/guestfs-libraries.m4 +++ b/m4/guestfs-libraries.m4 @@ -169,3 +169,6 @@ PKG_CHECK_MODULES([JANSSON], [jansson >= 2.7]) dnl Check for libosinfo (mandatory) PKG_CHECK_MODULES([LIBOSINFO], [libosinfo-1.0]) + +dnl Check for hwdata directory (containing pci.ids) (optional, for virt-drivers) +PKG_CHECK_VAR([HWDATA_PKGDATADIR], [hwdata], [pkgdatadir]) diff --git a/po/POTFILES-ml b/po/POTFILES-ml index 73984796f..7632f374d 100644 --- a/po/POTFILES-ml +++ b/po/POTFILES-ml @@ -79,6 +79,8 @@ dib/output_format_tgz.ml dib/output_format_vhd.ml dib/utils.ml drivers/drivers.ml +drivers/hwdata.ml +drivers/hwdata_config.ml get-kernel/get_kernel.ml resize/resize.ml sparsify/cmdline.ml -- 2.31.1