-
Notifications
You must be signed in to change notification settings - Fork 0
/
apu-ehci.c
186 lines (148 loc) · 3.85 KB
/
apu-ehci.c
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/* Tool to route USB Ports 8+9 to EHCI instead of XHCI on an AMD APU */
/* (C) 2022 by Harald Welte <[email protected]> */
/* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>. */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/io.h>
#include <sys/types.h>
#include <sys/stat.h>
#define IOPORT_PMINDEX 0xCD6
#define IOPORT_PMDATA 0xCD7
/* Page 938 of "BKDG for AMD Family 16h Models 30-3Fh Processors" */
#define PM_REG_USB_ENABLE 0xEF
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
/* ecx is often an input as well as an output. */
asm volatile("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx));
}
static bool compatible_cpu(void)
{
uint32_t eax, ebx, ecx, edx;
uint16_t model, family;
eax = 1;
native_cpuid(&eax, &ebx, &ecx, &edx);
family = (eax >> 8) & 0xf;
model = (eax >> 4) & 0xf;
if ((family == 6) | (family == 15))
model |= ((eax >> 16) & 0x0f) << 4;
if (family == 15)
family += ((eax >> 20) & 0xff);
printf("Detected CPU Family: 0x%x, Model: 0x%x\n", family, model);
if (family != 0x16)
return false;
if (model < 0x30 || model > 0x3f)
return false;
return true;
}
static uint8_t apu_read_pm_reg(uint8_t reg)
{
outb(reg, IOPORT_PMINDEX);
return inb(IOPORT_PMDATA);
}
static void apu_write_pm_reg(uint8_t reg, uint8_t val)
{
outb(reg, IOPORT_PMINDEX);
outb(val, IOPORT_PMDATA);
}
static int apu_write_pm_reg_verify(uint8_t reg, uint8_t val)
{
uint8_t val2;
apu_write_pm_reg(reg, val);
val2 = apu_read_pm_reg(reg);
if (val != val2) {
fprintf(stderr, "Wrote 0x%02x to register 0x%02x, but verify renders 0x%02x\n", val, reg, val2);
return -EIO;
}
return 0;
}
static int usb_port89_to_ehci(bool to_ehci)
{
uint8_t reg;
printf("Routing USB Ports 8+8 to %s\n", to_ehci ? "EHCI2" : "XHCI");
reg = apu_read_pm_reg(PM_REG_USB_ENABLE);
if (to_ehci)
reg &= ~0x80; /* route USB8+USB9 to EHCI */
else
reg |= 0x80;
return apu_write_pm_reg_verify(PM_REG_USB_ENABLE, reg);
}
static int ehci2_enable(bool enable)
{
uint8_t reg;
printf("%sabling USB EHCI Controller 2\n", enable ? "En" : "Dis");
reg = apu_read_pm_reg(PM_REG_USB_ENABLE);
if (enable)
reg |= 0x20;
else
reg &= ~0x20;
return apu_write_pm_reg_verify(PM_REG_USB_ENABLE, reg);
}
static int rescan_pci(void)
{
int rc, fd;
fd = open("/sys/bus/pci/rescan", O_WRONLY);
if (fd < 0) {
fprintf(stderr, "Error opening /sys/bus/pci/rescan: %s\n", strerror(errno));
return fd;
}
printf("Instructing kernel to re-scan PCI bus\n");
rc = write(fd, "1\n", 2);
if (rc != 2) {
fprintf(stderr, "Error writing to /sys/bus/pci/rescan: %s\n", strerror(errno));
close(fd);
return rc;
}
close(fd);
return 0;
}
static void print_usage(void)
{
printf("Usage: apu-ehci (enable|disable)\n");
}
int main(int argc, char **argv)
{
int rc;
rc = ioperm(IOPORT_PMINDEX, 2, 1);
if (rc < 0) {
fprintf(stderr, "Unalbe to access I/O ports: %s\n", strerror(errno));
return rc;
}
if (argc < 2) {
print_usage();
exit(0);
}
if (!compatible_cpu()) {
fprintf(stderr, "Your CPU is not an AMD APU compatible with this program\n");
exit(1);
}
if (!strcmp(argv[1], "enable")) {
usb_port89_to_ehci(true);
ehci2_enable(true);
rescan_pci();
} else if (!strcmp(argv[1], "disable")) {
usb_port89_to_ehci(false);
ehci2_enable(false);
rescan_pci();
} else {
print_usage();
exit(2);
}
}