summaryrefslogtreecommitdiff
path: root/pm/sleep.d/98-video-quirk-db-handler
blob: ec8c8920050841e275e1da91ef861ca71842c18f (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
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
#!/bin/bash
# Prototype video quirk database handler that does not rely on HAL.

shopt -s extglob

. "${PM_FUNCTIONS}"

[[ $PM_DEBUG ]] && { 
    export PS4='${BASH_SOURCE}@${LINENO}(${FUNCNAME[0]}): ';
    set -x
}

possible_video_quirks=" --quirk-dpms-on
	   --quirk-dpms-suspend
	   --quirk-s3-mode
	   --quirk-s3-bios
	   --quirk-vbe-post
	   --quirk-vbe-post
	   --quirk-vga-mode-3
	   --quirk-vbemode-restore
	   --quirk-vbestate-restore
	   --quirk-reset-brightness
	   --quirk-radeon-off
	   --quirk-no-fb
	   --quirk-save-pci"

possible_system_properties="system.firmware.version 
        system.firmware.vendor
	system.firmware.release_date
        system.hardware.vendor
	system.hardware.product 
        system.hardware.version
	system.board.product 
        system.board.version 
        system.board.vendor
	system.hardware.primary_video.vendor
	system.hardware.primary_video.product
	system.hardware.primary_video.driver
	system.hardware.primary_video.using_kms
	system.kernel.version"

has_video_parameters() {
    local p params=$(get_parameters)
    [[ $params ]] || return 1
    for p in $params; do
	[[ $possible_video_quirks = *$p* ]] && return
    done
    return 1
}

# video specific helper functions.

# Are we using the nVidia binary driver?
using_nvidia() {  [[ -d /sys/module/nvidia ]]; }

# How about the ATI one?
using_fglrx() { [[ -d /sys/module/fglrx ]]; }

# OK, what about a driver that is using kernel modesetting?
using_kms() { grep -q -E '(nouveau|drm)fb' /proc/fb; }

# Get some video related values when HAL has not gotten them for us, or 
# HAL is not available.
videoget() {
    local dev pci
    pci="/sys/bus/pci/devices"
    for dev in "$pci"/*; do
	[[ -f "${dev}/class" ]] || continue
	[[ "$(cat "${dev}/class")" = "0x030000" ]] || continue
	case $1 in
	    vendor) RES="$(cat "${dev}/vendor")" ;;
	    device) RES="$(cat "${dev}/device")" ;;
	    driver) 
		if [[ -L ${dev}/driver ]]; then
		    RES="$(readlink "${dev}/driver")"
		    RES="${RES##*/}"
		elif using_nvidia; then
		    RES=nvidia
		elif using_fglrx; then
		    RES=fglrx
		fi
		;;
	    using_kms)
		if using_kms; then
		    RES=true
		else
		    RES=false
		fi
		;;
	esac
	break
    done
}

# Get some important information about this system.

# If we have /sys/class/dmi/id, life is easy, and we do not need to 
# depend on HAL or dmidecode.
_dmisysget() {
    [[ -r /sys/class/dmi/id/$1 ]] || RES=""
    read RES < "/sys/class/dmi/id/$1"
}

dmisysget() {
    case $1 in 
	system.firmware.vendor) _dmisysget bios_vendor ;;
	system.firmware.version) _dmisysget bios_version ;;
	system.firmware.release_date) _dmisysget bios_date ;;
	system.hardware.vendor) _dmisysget sys_vendor ;;
	system.hardware.product) _dmisysget product_name ;;
	system.hardware.version) _dmisysget product_version ;;
	system.board.product) _dmisysget board_name ;;
	system.board.version) _dmisysget board_version ;;
	system.board.vendor) _dmisysget board_vendor ;;
	system.hardware.primary_video.vendor) videoget vendor ;;
	system.hardware.primary_video.product) videoget device ;;
	system.hardware.primary_video.driver) videoget driver ;;
	system.hardware.primary_video.using_kms) videoget using_kms ;;
	system.kernel.version) RES=$(uname -r) ;;
	*) return 1
    esac
}

# Get system information using dmidecode.  Slow and ugly, but
# should be supported just about everywhere.
_dmidecodeget() {
    RES=$(dmidecode -s $1)
}

dmidecodeget() {
    case $1 in 
	system.firmware.vendor) _dmidecodeget bios-vendor ;;
	system.firmware.version) _dmidecodeget bios-version ;;
	system.firmware.release_date) _dmidecodeget bios-release-date;;
	system.hardware.vendor) _dmidecodeget system-manufacturer;;
	system.hardware.product) _dmidecodeget system-product-name;;
	system.hardware.version) _dmidecodeget system-version;;
	system.board.product) _dmidecodeget baseboard-product-name;;
	system.board.version) _dmidecodeget baseboard-version;;
	system.board.vendor) _dmidecodeget baseboard-manufacturer;;
	*) return 1
    esac
}

# If we have HAL, it has already done most of the work for us.
halget() {
    local hgp="hal-get-property --udi /org/freedesktop/Hal/devices/computer --key" 
    case $1 in
	system.firmware.version) RES=$($hgp "$1") ;;
	system.firmware.vendor) RES=$($hgp "$1") ;;
	system.firmware.release_date) RES=$($hgp "$1") ;;
	system.hardware.vendor) RES=$($hgp "$1") ;;
	system.hardware.product) RES=$($hgp "$1") ;;
	system.hardware.version) RES=$($hgp "$1") ;;
	system.board.product) RES=$($hgp "$1") ;;
	system.board.version) RES=$($hgp "$1") ;;
	system.board.vendor) RES=$($hgp "$1") ;;
	*) return 1
    esac
}

canonicalize_dmivar() {
    [[ $1 =~ ^[a-z.-]+$ && $possible_system_properties = *$1* ]] || return 1
    echo "${1//[-.]/_}"
}

# Precache the DMI and other information we will need as shell variables.
# This will make things easier when we start running for real.
precache_dmivars() {
    local p q f
    for q in $possible_system_properties; do
	p=$(canonicalize_dmivar $q) || return 1
	RES=""
	for f in dmisysget halget dmidecodeget; do
	    "$f" "$q" && break || continue 2
	done
	RES="${RES##+( )}"
	RES="${RES%%+( )}"
	read "$p" <<<$RES
    done
    RES=""
}

# Use bash variable indirection to set RES to the actual parameter
# we are looking for. Sanity check the variable we were passed to
# keep things sane.
getprop() {
    RES=""
    local p
    if ! p=$(canonicalize_dmivar $1); then
	echo "Unable to obtain DMI information for $1" >&2
	exit 1
    fi
    RES="${!p}"
}

# test to see if the parameter passed is a decimal or hexidecimal number
# Note the complete lack of floating point support.
isnum() {
    [[ $1 =~ ^[0-9]+\$  || $1 =~ ^0[xX][0-9a-fA-F]+\$ ]]
}

# for all the matching functions, 
# $2 = the given constant (or regular expression),
# $1 = the raw data grabbed from HAL or dmidecode or wherever

regex() { [[ $1 =~ $2 ]]; }

regex_ncase() {
    local r
    shopt -s nocasematch
    regex "$1" "$2"
    r=$?
    shopt -u nocasematch
    return $r
}

regex_inverse() { ! regex "$1" "$2"; }
compare_eq() { [[ $1 = $2 ]]; }
compare_ne() { [[ $1 != $2 ]]; }
compare_gt() { [[ $1 > $2 ]]; }
compare_ge() { compare_eq "$@" || compare_gt "$@"; }
compare_lt() { [[ $1 < $2 ]]; }
compare_le() { compare_eq "$@" || compare_lt "$@"; }
numeric_compare_eq() { (( $1 == $2 )); }
numeric_compare_ne() { (( $1 != $2 )); }
numeric_compare_gt() { (( $1 > $2 )); }
numeric_compare_ge() { (( $1 >= $2 )); }
numeric_compare_lt() { (( $1 < $2 )); }
numeric_compare_le() { (( $1 <= $2 )); }
numeric_compare_eq_list() {
    local key val
	# $1 = key val to compare
	# $2 = list to compare to
    key=$1
    val="${2//;/ }"
    for x in $val; do
	(( $key == $x )) && return 0
    done
    return 1
}

# Helper function for nVidia g80 gpus.  They require extra special handling 
# when not using the nVidia binary driver.
have_nvidia_g80() {
    numeric_compare_eq $system_hardware_primary_video_vendor 0x10de || return
    numeric_compare_eq_list $system_hardware_primary_video_product \
	'0x190;0x191;0x192;0x193;0x194;0x195;0x196;0x197;0x198;0x199;0x19a;0x19b;0x19c;0x19d;0x19e;0x19f;0x400;0x401;0x402;0x403;0x404;0x405;0x406;0x407;0x408;0x409;0x40a;0x40b;0x40c;0x40d;0x40e;0x40f;0x420;0x421;0x422;0x423;0x424;0x425;0x426;0x427;0x428;0x429;0x42a;0x42b;0x42c;0x42d;0x42e;0x42f' || return
}

# Helper function for recent Intel framebuffer drivers.
have_smart_intel() {
    local kernel_rev=$system_kernel_revision
    local driver=$system_hardware_primary_video_driver
    # currently, intel kernel modesetting is not quite smart enough
    # we still need acpi s3 kernel modesetting hooks, so don't remove those
    # options if they were passed.
    [[ $driver = i915 && ( $kernel_rev > 2.6.26 || $kernel_rev = 2.6.26 ) ]]
}

# find the appropriate quirks for this system using the native-format
# quirks database. Since the database is tree-ish, we use some stupid
# recursion tricks.

# $1 = whether to ignore what we are reading
_find_native() {
    local action key matcher regex
    while read action key matcher regex; do
	[[ $action && ${action:0:1} != '#' ]] || continue
	case $action in
	    match) 
		if [[ $1 = ignore ]]; then
		    _find_native $1
		else
		    getprop "$key"
		    [[ $matcher && $regex ]] || find_native ignore
		# if this matcher matches, look at nodes farther out. 
		    if $matcher "$RES" "$regex"; then
			_find_native work
		    else
			_find_native ignore
		    fi
		fi
		;;
	    endmatch)
		[[ $found ]] && return 0 || return 1 ;;
	    addquirk) [[ $1 = ignore ]] && continue
		found=true
		add_parameters "$key"
		;;
	    delquirk) [[ $1 = ignore ]]&& continue
		found=true
		remove_parameters "$key"
		;;
	esac
    done
}

find_native() (
    [[ -f $1 ]] || return 1
    exec <"$1"
    _find_native work
)

# If we resumed, write out the quirks we used as our last known
# working ones for this hardware, kernel, driver, and KMS setting.
write_last_known_working() ( 
    local matcher quirk
    precache_dmivars
    exec >"$PM_LKW_QUIRKS"
    for prop in system.firmware.version system.firmware.vendor \
	system.firmware.release_date system.hardware.vendor \
	system.hardware.product system.hardware.version \
	system.board.product system.board.version system.board.vendor \
	system.hardware.primary_video.vendor \
	system.hardware.primary_video.product \
	system.hardware.primary_video.driver \
	system.hardware.primary_video.using_kms \
	system.kernel.version; do
	getprop "$prop"
	if isnum "$RES"; then
	    matcher=numeric_compare_eq
	else
	    matcher=compare_eq
	fi
	echo "match $prop $matcher ${RES}"
    done
    if [[ $QUIRKS ]]; then 
	for quirk in $QUIRKS; do
	    echo "addquirk $quirk"
	done
    else
	echo "addquirk --quirk-none"
    fi
    for ((x=1; x<14; x++)); do
	echo endmatch
    done
)

case $1 in
    suspend|hibernate)
        # Aaand.... GO
	# cache all the properties we will need.
	precache_dmivars

	# This logic can also be expressed using entries in the quirkdb,
	# but I am too lazy to do that until a final quirk database is 
	# formalized.
	if has_parameter --quirk-test && has_video_parameters; then
	    # The user is explicitly testing video parameters.
	    # Use them without the usual filtering. This may cause the system 
	    # to blow up, but they explicitly asked for it. 
	    remove_parameters --quirk-test
	elif using_kms; then
            # Using kernel modesetting?  No quirks, and do not change vts.
	    remove_parameters $possible_video_quirks
	    add_parameters --quirk-no-chvt
	elif using_nvidia; then
            # Ditto for nVidia binary drivers
	    remove_parameters $possible_video_quirks
	    add_parameters --quirk-no-chvt
	elif using_fglrx; then
	    # fglrx may or may not have to change vts, reports one
	    # way or the other welcome.
	    remove_parameters $possible_video_quirks
	    add_parameters --quirk-none
	elif have_nvidia_g80; then
            # nVidia G80 GPUs require special handling when not using nvidia
	    # binary drivers.  I do not know if noveau requires help or not.
	    remove_parameters $possible_video_quirks
	    add_parameters --quirk-vbe-post
	else
	    # Go ahead and get our quirks.
	    if has_video_parameters; then
	        # Parameters from the command line take precedence
		# over the database, so do not query it.
		:
	    elif [[ $PM_QUIRKS ]]; then
		# If we have $PM_QUIRKS. use it instead of the quirk database
		add_parameters $PM_QUIRKS
		# If we were not passed any quirks on the command line,
		# get them from the database.
	    elif QUIRKS=$(find_native "$PM_LKW_QUIRKS"); then
		# Known working quirks from our last run are still valid.
		# Use them.
		add_parameters $QUIRKS
	    else
		# Our known working quirks from the last run are either
		# nonexistent or invalid.  Either way, start over.
		rm "$PM_LKW_QUIRKS" >/dev/null 2>&1
		for f in "$PM_QUIRKDB"/*.quirkdb
		do
		    QUIRKS=$(find_native "$f") && break
		done
		# some default quirks if we did not get any.
		[[ -z $QUIRKS ]] && QUIRKS="--quirk-vbe-post 
                                        --quirk-dpms-on --quirk-dpms-suspend 
                                        --quirk-vbestate-restore
			                --quirk-vbemode-restore 
                                        --quirk-vga-mode-3"
		add_parameters $QUIRKS
		savestate video_quirks "$QUIRKS"
	    fi
	    if have_smart_intel; then
	       # Intel without KMS does not require most quirks, no matter
	       # what anything else says.  The only ones that seem to 
	       # matter are the --quirk-s3 ones, so remove everything else.
		remove_parameters --quirk-dpms-on \
		    --quirk-dpms-suspend \
		    --quirk-vbe-post \
		    --quirk-vbe-post \
		    --quirk-vga-mode-3 \
		    --quirk-vbemode-restore \
		    --quirk-vbestate-restore \
		    --quirk-reset-brightness \
		    --quirk-radeon-off \
		    --quirk-no-fb \
		    --quirk-save-pci
	    fi
	fi
	;;
    thaw|resume)
	if state_exists video_quirks; then
	    QUIRKS=$(restorestate video_quirks);
	    write_last_known_working
	elif has_parameter --store-quirks-as-lkw; then
	    for x in $(get_parameters); do
		for y in $possible_video_quirks; do
		    [[ $x = $y ]] && QUIRKS=" $QUIRKS $x"
		done
	    done
	    write_last_known_working
	fi
	;;
esac