aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/screen_info_pci.c
blob: 6c58335171410029d22cc62f4bb1288352258ee9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// SPDX-License-Identifier: GPL-2.0

#include <linux/pci.h>
#include <linux/printk.h>
#include <linux/screen_info.h>
#include <linux/string.h>

static struct pci_dev *screen_info_lfb_pdev;
static size_t screen_info_lfb_bar;
static resource_size_t screen_info_lfb_offset;
static struct resource screen_info_lfb_res = DEFINE_RES_MEM(0, 0);

static bool __screen_info_relocation_is_valid(const struct screen_info *si, struct resource *pr)
{
	u64 size = __screen_info_lfb_size(si, screen_info_video_type(si));

	if (screen_info_lfb_offset > resource_size(pr))
		return false;
	if (size > resource_size(pr))
		return false;
	if (resource_size(pr) - size < screen_info_lfb_offset)
		return false;

	return true;
}

void screen_info_apply_fixups(void)
{
	struct screen_info *si = &screen_info;

	if (screen_info_lfb_pdev) {
		struct resource *pr = &screen_info_lfb_pdev->resource[screen_info_lfb_bar];

		if (pr->start != screen_info_lfb_res.start) {
			if (__screen_info_relocation_is_valid(si, pr)) {
				/*
				 * Only update base if we have an actual
				 * relocation to a valid I/O range.
				 */
				__screen_info_set_lfb_base(si, pr->start + screen_info_lfb_offset);
				pr_info("Relocating firmware framebuffer to offset %pa[d] within %pr\n",
					&screen_info_lfb_offset, pr);
			} else {
				pr_warn("Invalid relocating, disabling firmware framebuffer\n");
			}
		}
	}
}

static void screen_info_fixup_lfb(struct pci_dev *pdev)
{
	unsigned int type;
	struct resource res[SCREEN_INFO_MAX_RESOURCES];
	size_t i, numres;
	int ret;
	const struct screen_info *si = &screen_info;

	if (screen_info_lfb_pdev)
		return; // already found

	type = screen_info_video_type(si);
	if (type != VIDEO_TYPE_EFI)
		return; // only applies to EFI

	ret = screen_info_resources(si, res, ARRAY_SIZE(res));
	if (ret < 0)
		return;
	numres = ret;

	for (i = 0; i < numres; ++i) {
		struct resource *r = &res[i];
		const struct resource *pr;

		if (!(r->flags & IORESOURCE_MEM))
			continue;
		pr = pci_find_resource(pdev, r);
		if (!pr)
			continue;

		/*
		 * We've found a PCI device with the framebuffer
		 * resource. Store away the parameters to track
		 * relocation of the framebuffer aperture.
		 */
		screen_info_lfb_pdev = pdev;
		screen_info_lfb_bar = pr - pdev->resource;
		screen_info_lfb_offset = r->start - pr->start;
		memcpy(&screen_info_lfb_res, r, sizeof(screen_info_lfb_res));
	}
}
DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY, 16,
			       screen_info_fixup_lfb);

static struct pci_dev *__screen_info_pci_dev(struct resource *res)
{
	struct pci_dev *pdev = NULL;
	const struct resource *r = NULL;

	if (!(res->flags & IORESOURCE_MEM))
		return NULL;

	while (!r && (pdev = pci_get_base_class(PCI_BASE_CLASS_DISPLAY, pdev))) {
		r = pci_find_resource(pdev, res);
	}

	return pdev;
}

/**
 * screen_info_pci_dev() - Return PCI parent device that contains screen_info's framebuffer
 * @si: the screen_info
 *
 * Returns:
 * The screen_info's parent device or NULL on success, or a pointer-encoded
 * errno value otherwise. The value NULL is not an error. It signals that no
 * PCI device has been found.
 */
struct pci_dev *screen_info_pci_dev(const struct screen_info *si)
{
	struct resource res[SCREEN_INFO_MAX_RESOURCES];
	ssize_t i, numres;

	numres = screen_info_resources(si, res, ARRAY_SIZE(res));
	if (numres < 0)
		return ERR_PTR(numres);

	for (i = 0; i < numres; ++i) {
		struct pci_dev *pdev = __screen_info_pci_dev(&res[i]);

		if (pdev)
			return pdev;
	}

	return NULL;
}
EXPORT_SYMBOL(screen_info_pci_dev);