1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | #include <linux/pci.h> |
4 | #include <linux/printk.h> |
5 | #include <linux/screen_info.h> |
6 | #include <linux/string.h> |
7 | |
8 | static struct pci_dev *screen_info_lfb_pdev; |
9 | static size_t screen_info_lfb_bar; |
10 | static resource_size_t screen_info_lfb_offset; |
11 | static struct resource screen_info_lfb_res = DEFINE_RES_MEM(0, 0); |
12 | |
13 | static bool __screen_info_relocation_is_valid(const struct screen_info *si, struct resource *pr) |
14 | { |
15 | u64 size = __screen_info_lfb_size(si, type: screen_info_video_type(si)); |
16 | |
17 | if (screen_info_lfb_offset > resource_size(res: pr)) |
18 | return false; |
19 | if (size > resource_size(res: pr)) |
20 | return false; |
21 | if (resource_size(res: pr) - size < screen_info_lfb_offset) |
22 | return false; |
23 | |
24 | return true; |
25 | } |
26 | |
27 | void screen_info_apply_fixups(void) |
28 | { |
29 | struct screen_info *si = &screen_info; |
30 | |
31 | if (screen_info_lfb_pdev) { |
32 | struct resource *pr = &screen_info_lfb_pdev->resource[screen_info_lfb_bar]; |
33 | |
34 | if (pr->start != screen_info_lfb_res.start) { |
35 | if (__screen_info_relocation_is_valid(si, pr)) { |
36 | /* |
37 | * Only update base if we have an actual |
38 | * relocation to a valid I/O range. |
39 | */ |
40 | __screen_info_set_lfb_base(si, lfb_base: pr->start + screen_info_lfb_offset); |
41 | pr_info("Relocating firmware framebuffer to offset %pa[d] within %pr\n" , |
42 | &screen_info_lfb_offset, pr); |
43 | } else { |
44 | pr_warn("Invalid relocating, disabling firmware framebuffer\n" ); |
45 | } |
46 | } |
47 | } |
48 | } |
49 | |
50 | static void screen_info_fixup_lfb(struct pci_dev *pdev) |
51 | { |
52 | unsigned int type; |
53 | struct resource res[SCREEN_INFO_MAX_RESOURCES]; |
54 | size_t i, numres; |
55 | int ret; |
56 | const struct screen_info *si = &screen_info; |
57 | |
58 | if (screen_info_lfb_pdev) |
59 | return; // already found |
60 | |
61 | type = screen_info_video_type(si); |
62 | if (type != VIDEO_TYPE_EFI) |
63 | return; // only applies to EFI |
64 | |
65 | ret = screen_info_resources(si, r: res, ARRAY_SIZE(res)); |
66 | if (ret < 0) |
67 | return; |
68 | numres = ret; |
69 | |
70 | for (i = 0; i < numres; ++i) { |
71 | struct resource *r = &res[i]; |
72 | const struct resource *pr; |
73 | |
74 | if (!(r->flags & IORESOURCE_MEM)) |
75 | continue; |
76 | pr = pci_find_resource(dev: pdev, res: r); |
77 | if (!pr) |
78 | continue; |
79 | |
80 | /* |
81 | * We've found a PCI device with the framebuffer |
82 | * resource. Store away the parameters to track |
83 | * relocation of the framebuffer aperture. |
84 | */ |
85 | screen_info_lfb_pdev = pdev; |
86 | screen_info_lfb_bar = pr - pdev->resource; |
87 | screen_info_lfb_offset = r->start - pr->start; |
88 | memcpy(&screen_info_lfb_res, r, sizeof(screen_info_lfb_res)); |
89 | } |
90 | } |
91 | DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY, 16, |
92 | screen_info_fixup_lfb); |
93 | |
94 | static struct pci_dev *__screen_info_pci_dev(struct resource *res) |
95 | { |
96 | struct pci_dev *pdev = NULL; |
97 | const struct resource *r = NULL; |
98 | |
99 | if (!(res->flags & IORESOURCE_MEM)) |
100 | return NULL; |
101 | |
102 | while (!r && (pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, from: pdev))) { |
103 | r = pci_find_resource(dev: pdev, res); |
104 | } |
105 | |
106 | return pdev; |
107 | } |
108 | |
109 | /** |
110 | * screen_info_pci_dev() - Return PCI parent device that contains screen_info's framebuffer |
111 | * @si: the screen_info |
112 | * |
113 | * Returns: |
114 | * The screen_info's parent device or NULL on success, or a pointer-encoded |
115 | * errno value otherwise. The value NULL is not an error. It signals that no |
116 | * PCI device has been found. |
117 | */ |
118 | struct pci_dev *screen_info_pci_dev(const struct screen_info *si) |
119 | { |
120 | struct resource res[SCREEN_INFO_MAX_RESOURCES]; |
121 | ssize_t i, numres; |
122 | |
123 | numres = screen_info_resources(si, r: res, ARRAY_SIZE(res)); |
124 | if (numres < 0) |
125 | return ERR_PTR(error: numres); |
126 | |
127 | for (i = 0; i < numres; ++i) { |
128 | struct pci_dev *pdev = __screen_info_pci_dev(res: &res[i]); |
129 | |
130 | if (pdev) |
131 | return pdev; |
132 | } |
133 | |
134 | return NULL; |
135 | } |
136 | EXPORT_SYMBOL(screen_info_pci_dev); |
137 | |