Commit | Line | Data |
---|---|---|
036105e3 TZ |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | ||
3 | #include <linux/pci.h> | |
78aa89d1 | 4 | #include <linux/printk.h> |
036105e3 | 5 | #include <linux/screen_info.h> |
78aa89d1 TZ |
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, screen_info_video_type(si)); | |
16 | ||
17 | if (screen_info_lfb_offset > resource_size(pr)) | |
18 | return false; | |
19 | if (size > resource_size(pr)) | |
20 | return false; | |
21 | if (resource_size(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, 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, 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(pdev, 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); | |
036105e3 TZ |
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, pdev))) { | |
103 | r = pci_find_resource(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, res, ARRAY_SIZE(res)); | |
124 | if (numres < 0) | |
125 | return ERR_PTR(numres); | |
126 | ||
127 | for (i = 0; i < numres; ++i) { | |
128 | struct pci_dev *pdev = __screen_info_pci_dev(&res[i]); | |
129 | ||
130 | if (pdev) | |
131 | return pdev; | |
132 | } | |
133 | ||
134 | return NULL; | |
135 | } | |
136 | EXPORT_SYMBOL(screen_info_pci_dev); |