projects/linux·blob master · refs

197 lines · 4.2 KiB

1// SPDX-License-Identifier: GPL-2.0
2
3#include <linux/export.h>
4#include <linux/fb.h>
5#include <linux/module.h>
6#include <linux/uaccess.h>
7
8ssize_t fb_io_read(struct fb_info *info, char __user *buf, size_t count, loff_t *ppos)
9{
10 unsigned long p = *ppos;
11 u8 *buffer, *dst;
12 u8 __iomem *src;
13 int c, cnt = 0, err = 0;
14 unsigned long total_size, trailing;
15
16 if (info->flags & FBINFO_VIRTFB)
17 fb_warn_once(info, "Framebuffer is not in I/O address space.");
18
19 if (!info->screen_base)
20 return -ENODEV;
21
22 total_size = info->screen_size;
23
24 if (total_size == 0)
25 total_size = info->fix.smem_len;
26
27 /*
28 * Security Hardening: Defend against buggy legacy drivers that may
29 * calculate a malformed screen_size. Clamp total_size to the actual
30 * hardware mapped memory limit (smem_len) to prevent OOB access.
31 */
32 if (info->fix.smem_len && total_size > info->fix.smem_len)
33 total_size = info->fix.smem_len;
34
35 if (p >= total_size)
36 return 0;
37
38 if (count >= total_size)
39 count = total_size;
40
41 if (count + p > total_size)
42 count = total_size - p;
43
44 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
45 GFP_KERNEL);
46 if (!buffer)
47 return -ENOMEM;
48
49 src = (u8 __iomem *) (info->screen_base + p);
50
51 if (info->fbops->fb_sync)
52 info->fbops->fb_sync(info);
53
54 while (count) {
55 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
56 dst = buffer;
57 fb_memcpy_fromio(dst, src, c);
58 dst += c;
59 src += c;
60
61 trailing = copy_to_user(buf, buffer, c);
62 if (trailing == c) {
63 err = -EFAULT;
64 break;
65 }
66 c -= trailing;
67
68 *ppos += c;
69 buf += c;
70 cnt += c;
71 count -= c;
72
73 /*
74 * If there was a partial copy, the user buffer is faulty.
75 * Break out to avoid over-advancing the src pointer and
76 * reading out of bounds in the next iteration.
77 */
78 if (trailing)
79 break;
80 }
81
82 kfree(buffer);
83
84 return cnt ? cnt : err;
85}
86EXPORT_SYMBOL(fb_io_read);
87
88ssize_t fb_io_write(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos)
89{
90 unsigned long p = *ppos;
91 u8 *buffer, *src;
92 u8 __iomem *dst;
93 int c, cnt = 0, err = 0;
94 unsigned long total_size, trailing;
95
96 if (info->flags & FBINFO_VIRTFB)
97 fb_warn_once(info, "Framebuffer is not in I/O address space.");
98
99 if (!info->screen_base)
100 return -ENODEV;
101
102 total_size = info->screen_size;
103
104 if (total_size == 0)
105 total_size = info->fix.smem_len;
106
107 /*
108 * Security Hardening: Defend against buggy legacy drivers that may
109 * calculate a malformed screen_size. Clamp total_size to the actual
110 * hardware mapped memory limit (smem_len) to prevent OOB access.
111 */
112 if (info->fix.smem_len && total_size > info->fix.smem_len)
113 total_size = info->fix.smem_len;
114
115 if (p > total_size)
116 return -EFBIG;
117
118 if (count > total_size) {
119 err = -EFBIG;
120 count = total_size;
121 }
122
123 if (count + p > total_size) {
124 if (!err)
125 err = -ENOSPC;
126
127 count = total_size - p;
128 }
129
130 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
131 GFP_KERNEL);
132 if (!buffer)
133 return -ENOMEM;
134
135 dst = (u8 __iomem *) (info->screen_base + p);
136
137 if (info->fbops->fb_sync)
138 info->fbops->fb_sync(info);
139
140 while (count) {
141 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
142 src = buffer;
143
144 trailing = copy_from_user(src, buf, c);
145 if (trailing == c) {
146 err = -EFAULT;
147 break;
148 }
149 c -= trailing;
150
151 fb_memcpy_toio(dst, src, c);
152 dst += c;
153 src += c;
154 *ppos += c;
155 buf += c;
156 cnt += c;
157 count -= c;
158 }
159
160 kfree(buffer);
161
162 return (cnt) ? cnt : err;
163}
164EXPORT_SYMBOL(fb_io_write);
165
166int fb_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
167{
168 unsigned long start = info->fix.smem_start;
169 u32 len = info->fix.smem_len;
170 unsigned long mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT;
171
172 if (info->flags & FBINFO_VIRTFB)
173 fb_warn_once(info, "Framebuffer is not in I/O address space.");
174
175 /*
176 * This can be either the framebuffer mapping, or if pgoff points
177 * past it, the mmio mapping.
178 */
179 if (vma->vm_pgoff >= mmio_pgoff) {
180 if (info->var.accel_flags)
181 return -EINVAL;
182
183 vma->vm_pgoff -= mmio_pgoff;
184 start = info->fix.mmio_start;
185 len = info->fix.mmio_len;
186 }
187
188 vma->vm_page_prot = vma_get_page_prot(vma);
189 vma->vm_page_prot = pgprot_framebuffer(vma->vm_page_prot, vma->vm_start,
190 vma->vm_end, start);
191
192 return vm_iomap_memory(vma, start, len);
193}
194EXPORT_SYMBOL(fb_io_mmap);
195
196MODULE_DESCRIPTION("Fbdev helpers for framebuffers in I/O memory");
197MODULE_LICENSE("GPL");