forked from hertg/egpu-switcher
-
Notifications
You must be signed in to change notification settings - Fork 0
/
egpu-switcher
executable file
Β·579 lines (469 loc) Β· 14.7 KB
/
egpu-switcher
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
#!/usr/bin/env bash
set -o errexit # exit script if a command fails
set -o nounset # exit when trying to use undeclared variables
#set -o xtrace # debug
# define some colors
declare red='\033[1;31m'
declare yellow='\033[1;33m'
declare green='\033[1;32m'
declare blue='\033[1;34m'
declare blank='\033[0m'
# define log level prefix
declare error="$red[error]$blank"
declare warn="$yellow[warn]$blank"
declare success="$green[success]$blank"
declare info="$blue[info]$blank"
# misc
declare datetime=$(date '+%Y%m%d%H%M%S')
declare number_regex='^[0-9]+$'
# constant variables for X-Server files
declare xdir=/etc/X11
declare xfile=$xdir/xorg.conf
declare xfile_egpu=$xdir/xorg.conf.egpu
declare xfile_internal=$xdir/xorg.conf.internal
declare xfile_backup=$xfile.backup
# constant variables for temp files
declare tmp_template=/usr/share/egpu-switcher/xorg.conf.template
# constant variables for systemd files
declare systemd_template=/usr/share/egpu-switcher/egpu.service
declare systemd_folder=/etc/systemd/system
# todo: this assumes that there is bash 4+ installed
declare -A gpus=()
declare gpu_connected=0
# helper method for printing error messages
function print_error() {
echo -e "$error $1"
}
# helper method for printing info messages
function print_info() {
echo -e "$info $1"
}
# helper method for printing warn messages
function print_warn() {
echo -e "$warn $1"
}
# helper method for printin success messages
function print_success() {
echo -e "$success $1"
}
# config
config_dir=/etc/egpu-switcher
config_file=${config_dir}/egpu-switcher.conf
typeset -A config
config=(
[internal_gpu]=""
[internal_driver]=""
[external_gpu]=""
[external_driver]=""
)
# read from config file
if [ -f $config_file ]; then
#print_info "Reading contents of your current configuration file at '${config_file}'"
while read line
do
if echo $line | grep -F = &>/dev/null
then
varname=$(echo "$line" | cut -d '=' -f 1)
config[$varname]=$(echo "$line" | cut -d '=' -f 2-)
fi
done < $config_file
fi
# check if the script is run as root
if [[ $EUID -ne 0 ]]; then
print_error "You need to run the script with root privileges"
exit
fi
# check if an argument was passed
if [ -z ${1+x} ]; then
print_error "No argument passed."
exit
fi
# read the connected GPUs and put them into the "gpus" associative array
function read_gpus() {
# empty the gpus array
gpus=()
declare lines=$(lspci -d ::0300 && lspci -d ::0302)
while read -r line ; do
declare name=$(echo $line | grep -o -e "[^:]*$")
declare bus=$(echo $line | grep -o -e "^[^ ]*")
# The bus IDs in hex
declare bus1h=${bus:0:2}
declare bus2h=${bus:3:2}
declare bus3h=${bus:6:1}
# The bus IDs in dec
declare bus1d=$((16#$bus1h))
declare bus2d=$((16#$bus2h))
declare bus3d=$((16#$bus3h))
# Remove the whitespace at the beginning of the name
# And concatenate bus IDs
bus="${bus1d}:${bus2d}:${bus3d}"
name=${name:1}
# Put the result into the gpus array
gpus+=( [$bus]=$name )
done <<< $lines
return
}
# returns 1 if egpu is connected, 0 if not
function is_egpu_connected() {
# read pci id from xorg.conf.egpu
declare egpu_pci_id=$(cat $xfile_egpu | grep -Ei "BusID" | grep -oEi '[0-9]+\:[0-9]+\:[0-9]+')
# create an array by splitting the BUS-ID on ':'
declare busArray=(${egpu_pci_id//:/ })
declare bus1d=${busArray[0]}
declare bus2d=${busArray[1]}
declare bus3d=${busArray[2]}
# convert dec to hex
declare bus1h=$(printf "%02x" $bus1d)
declare bus2h=$(printf "%02x" $bus2d)
declare bus3h=$(printf "%01x" $bus3d)
# instantiate counter
declare i=1
# begin infinite loop to allow retries if the egpu isn't connected immediately on bootup
while [ true ]; do
# if a video device is connected to the BUS-ID
if [ $( (lspci -d ::0300 && lspci -d ::0302) | grep -iEc "$bus1h:$bus2h.$bus3h") -eq 1 ]; then
print_info "EGPU is ${green}connected${blank}."
gpu_connected=1
hex_id=$bus1h:$bus2h.$bus3h
break
else
# escape the infinite loop after a certain amount of retries
if [ $i -ge 6 ]; then
print_info "EGPU is ${red}disconnected${blank}."
gpu_connected=0
hex_id=$bus1h:$bus2h.$bus3h
break
fi
fi
# increase counter by 1
i=$(( $i + 1 ))
# sleep for 500ms before retrying
sleep 0.5
done
}
# get the matching driver according to the gpu name
function get_driver() {
input=${1}
if [ $(echo "${input}" | grep -Eic "nvidia") -gt 0 ]; then
echo "nvidia"
return
fi
if [ $(echo "${input}" | grep -Eic "intel") -gt 0 ]; then
echo "intel"
return
fi
if [ $(echo "${input}" | grep -Eic "amd") -gt 0 ]; then
echo "amdgpu"
return
fi
}
# prompts the user to define their external/internal GPUs
# and saves it into the config file
function configure() {
# reset current config
config[internal_gpu]=""
config[internal_driver]=""
config[external_gpu]=""
config[external_driver]=""
# read currently attached GPUs
read_gpus
# save the number of lines to a variable
declare num_of_gpus=${#gpus[@]}
# additional check
if [ $num_of_gpus -lt "2" ]; then
print_warn "Only ${num_of_gpus} GPUs found, there need to be at least 2. Make sure to connect your EGPU for the setup."
exit
fi
# print the GPUs found
echo ""
echo -e "Found $num_of_gpus possible GPUs..."
echo ""
declare mapping=()
declare i=0
for key in ${!gpus[@]}; do
i=$((i+1))
mapping+=([${i}]=${key})
echo " $i: ${gpus[${key}]} (${key})"
done
echo ""
printf "Would you like to define a specific$green INTERNAL$blank GPU? (not recommended) [y/N]: "
read specify_internal
if [[ $specify_internal == "y" ]]; then
# prompt to choose the internal gpu from the list
printf "Choose your preferred$green INTERNAL$blank GPU [1-$num_of_gpus]: "
read internal
declare full_internal=${gpus[${mapping[$internal]}]}
declare pci_internal=${mapping[$internal]}
if ! [[ $internal =~ $number_regex ]] || [ -z "$pci_internal" ]; then
print_error "Your input is invalid. Exiting setup..."
exit
fi
config[internal_gpu]=${pci_internal}
config[internal_driver]=$(get_driver "$full_internal")
if [ -z ${config[internal_driver]} ]; then
print_info "Could not parse manufacturer from \"$full_internal\"."
printf "Please manually enter the driver to be used: "
read driver
config[internal_driver]=${driver}
fi
fi
# prompt to choose the external gpu from the list
printf "Choose your preferred$green EXTERNAL$blank GPU [1-$num_of_gpus]: "
read external
declare full_external=${gpus[${mapping[$external]}]}
declare pci_external=${mapping[$external]}
if ! [[ $external =~ $number_regex ]] || [ -z "$pci_external" ]; then
print_error "Your input is invalid. Exiting setup..."
exit
fi
config[external_gpu]=${pci_external}
config[external_driver]=$(get_driver "$full_external")
if [ -z "${config[external_driver]}" ]; then
print_info "Could not parse manufacturer from \"$full_external\"."
printf "Please manually enter the driver to be used: "
read driver
config[external_driver]=${driver}
fi
echo ""
# create config directory if it doesnt exist
mkdir -p $config_dir
# empty current config file
true > $config_file
# write new configurations to config file
for key in ${!config[@]}; do
echo "${key}=${config[${key}]}" >> $config_file
done
print_info "Saved new configuration to ${config_file}"
}
function setup() {
declare override=${1}
declare noprompt=${2}
# check if the template/script files can be found
if [ ! -f $tmp_template ]; then
print_error "The file $tmp_template wasn't found."
exit
fi
if [ -z ${config[external_gpu]} ]; then
if [ ${noprompt} -eq 1 ]; then
print_warn "It seems like you haven't configured egpu-switcher yet. Please run 'egpu-switcher setup' first."
exit
else
configure
fi
else
print_info "Using existing configuration file at '${config_file}''."
print_info "If you want to reconfigure egpu-switcher, please run 'egpu-switcher config'."
fi
# create the internal xorg config file
if [ ! -z ${config[internal_gpu]} ]; then
cp $tmp_template $xfile_internal
sed -i -e 's/\$BUS/'${config[internal_gpu]}'/g' -e 's/\$DRIVER/'${config[internal_driver]}'/g' -e 's/\$ID/Device0/g' $xfile_internal
else
true > $xfile_internal
fi
# create the external xorg config file
cp $tmp_template $xfile_egpu
sed -i -e 's/\$BUS/'${config[external_gpu]}'/g' -e 's/\$DRIVER/'${config[external_driver]}'/g' -e 's/\$ID/Device0/g' $xfile_egpu
# Executing the switch command to create the xorg.conf file
switch auto ${override}
# setup startup script
cp $systemd_template $systemd_folder
systemctl daemon-reload
systemctl enable egpu.service
print_success "Done... Setup finished"
}
function switch() {
declare mode=${1}
declare override=${2}
# Check if the xorg.conf files for internal and egpu exist
if ! [ -f $xfile_egpu ] || ! [ -f $xfile_internal ]; then
print_error "The xorg.conf files for egpu and internal do not exist. Run the setup first."
return
fi
# Check if there is a xorg.conf file, and back it up
if [ -f $xfile ] && ! [ -L $xfile ]; then
print_warn "The $xfile file already exists. Saving a backup to $xfile_backup.$datetime"
cp "$xfile" "$xfile_backup.$datetime"
fi
# if no parameter was passed to the method
if [ ${mode} = "auto" ]; then
print_info "Automatically detecting if egpu is connected... "
is_egpu_connected
if [ ${gpu_connected} = 1 ] ; then
mode="egpu"
else
mode="internal"
fi
fi
# when mode is 'egpu' and no 'nvidia' driver is used
if [ ${mode} = "egpu" ] && ! grep -Eiq 'Driver.*nvidia' $xfile_egpu; then
if [ -z ${hex_id+x} ]; then
is_egpu_connected
fi
declare disp_path=/sys/bus/pci/devices/[0-9a-f:]*${hex_id}/drm/card[0-9]*/card[0-9]*-*/status
declare disp_num=0
declare disp_disconnect=0
for disp in ${disp_path}; do
if [ -e $disp ]; then
((disp_num = $disp_num + 1)) || true
((disp_disconnect = $disp_disconnect + $(cat $disp | grep -ce ^disconnected$))) || true
fi
done
if [ $disp_disconnect -eq $disp_num ] && [ $disp_num -gt 0 ]; then
print_warn "No eGPU attached display detected with open source drivers. (Of ${disp_num} eGPU outputs detected) Internal mode and setting DRI_PRIME variable are recommended for this configuration."
if [ ${override} -eq 1 ]; then
print_warn "-> Overridden: Setting eGPU mode"
mode="egpu"
else
print_warn "Run 'egpu-switcher switch egpu --override' to force loading eGPU mode"
print_warn "-> Not setting eGPU mode."
mode="internal"
fi
fi
fi
if [ ${mode} = "egpu" ]; then
print_info "Create symlink ${green}${xfile}${blank} -> ${xfile_egpu}"
ln -sf ${xfile_egpu} ${xfile}
return
fi
if [ ${mode} = "internal" ]; then
print_info "Create symlink ${green}${xfile}${blank} -> ${xfile_internal}"
ln -sf ${xfile_internal} ${xfile}
return
fi
print_error "The argument '${mode}' that was passed to the switch() method is not valid."
}
function remove() {
if [ -z ${hex_id+x} ]; then
is_egpu_connected
fi
if [ $gpu_connected = 0 ]; then
print_error "No eGPU detected at BusID specified in xorg.conf.egpu. Stopping removal."
exit
fi
# Find GPU and audio devices
declare device_id=$(echo ${hex_id} | cut -f 1 -d '.').
declare device_path=/sys/bus/pci/devices/[0-9a-f:]*${device_id}[0-9]*/remove
declare vga_driver=$(cat $xfile_egpu | grep -Ei "Driver" | cut -f 2 -d \")
# Do actual GPU removal
( trap '' HUP TERM
while [ "$(systemctl status display-manager | awk '/Active:/{print$2}')" \
= "active" ]; do
sleep 1
done
if [ ${vga_driver} = "nvidia" ]; then
systemctl stop nvidia-persistenced.service
if [ $(lsmod | grep "nvidia_uvm " | awk '{print $3}') -gt 0 ] || [ $(lsmod | grep "nvidia_drm " | awk '{print $3}') -gt 0 ]; then
systemctl start display-manager.service
print_error "Driver still in use. Check for applications like Folding@Home running in the background. Stopping removal."
exit
fi
for drivers in nvidia_uvm nvidia_drm nvidia_modeset nvidia; do
modprobe -r ${drivers}
done
else
if [ $(lsmod | grep "${vga_driver} " | awk '{print $3}') -gt 0 ]; then
systemctl start display-manager.service
print_error "Driver still in use. Check for applications like Folding@Home running in the background. Stopping removal."
exit
fi
modprobe -r ${vga_driver}
fi
for dev_paths in ${device_path}; do
if [ -e $dev_paths ]; then
echo 1 > $dev_paths
fi
done
if [ $(lspci -k | grep -c ${vga_driver}) -gt 0 ]; then
modprobe ${vga_driver}
if [ ${vga_driver} = "nvidia" ]; then
modprobe nvidia_drm
fi
sleep 1
fi
systemctl start display-manager.service ) &
systemctl stop display-manager.service
}
function cleanup() {
declare hard=${1}
print_info "Starting cleanup process"
rm -f ${xfile_egpu}
rm -f ${xfile_internal}
# delete the xorg.conf file, if it is a symlink and restore the last backup
if [ -L ${xfile} ]; then
rm -f ${xfile}
if [ -e ${xfile_backup}.* ]; then
local lastbackup=$(ls -t ${xfile_backup}.* | head -1)
print_info "Restoring latest backup ${green}${lastbackup}"
mv ${lastbackup} ${xfile}
fi
fi
if [ ${hard} -eq 1 ]; then
print_info "Removing configuration files (--hard)"
rm -f ${config_file}
rm -fd ${config_dir}
fi
# only try to stop the egpu.service if its loaded.
# note: using sed rather than the --value property on purpose, to support older versions of systemd
if [ $(sudo systemctl show -p LoadState egpu.service | sed 's/LoadState=//g') == "loaded" ]; then
print_info "Removing the 'egpu.service' systemd service"
systemctl stop egpu.service
systemctl disable egpu.service
rm ${systemd_folder}/egpu.service
systemctl daemon-reload
systemctl reset-failed
fi
print_success "Done... Finished cleanup"
}
if [ $1 = "setup" ]; then
declare override=0
declare noprompt=0
if [ $# -gt 1 ]; then
for option in "$@"
do
if [ ${option} = "--override" ]; then
override=1
elif [ ${option} = "--noprompt" ]; then
noprompt=1
fi
done
fi
setup ${override} ${noprompt}
elif [ $1 = "switch" ]; then
declare override=0
if [ $# -lt 2 ]; then
print_error "No argument passed to the switch method. Possible options: auto, egpu, internal"
exit
else
for option in "$@"
do
if [ ${option} = "--override" ]; then
override=1
fi
done
fi
switch ${2} ${override}
elif [ $1 = "config" ]; then
configure
elif [ $1 = "cleanup" ]; then
declare hard=0
if [ $# -gt 1 ]; then
for option in "$@"
do
if [ ${option} = "--hard" ]; then
hard=1
fi
done
fi
cleanup ${hard}
elif [ $1 = "remove" ]; then
print_warn "This will switch to internal mode, remove the eGPU PCIe addresses and log out all users. Continue? [y/N]: "
read specify_remove
if [[ $specify_remove == "y" ]]; then
switch internal 0
remove
fi
else
print_error "Unknown argument '$1'.\navailable commands: setup, switch, config, cleanup, remove"
fi
# systemctl restart display-manager.service