Skip to content

Conversation

@ean365
Copy link
Contributor

@ean365 ean365 commented Jan 10, 2026

Odroid HC4 is a NAS. Needs to park HDD heads prior to reboot or shutdown. This simple PR adds the already existing odroid.shutdown bsp script (originally used for the XU4) to the meson-sm1.conf file for the HC4.

I have successfully tested the odroid.shutdown script on the HC4 with HDDs, and it properly parks the heads. Without this, the heads are forced to do an emergency retract, which is a violent action that degrades the HDD over time.

Summary by CodeRabbit

  • Chores
    • Added system-level configuration enhancements for improved shutdown handling on select devices.

✏️ Tip: You can customize this high-level summary in your review settings.

Odroid HC4 is a NAS.  Needs to park HDD heads prior to reboot or shutdown.
@ean365 ean365 requested a review from igorpecovnik as a code owner January 10, 2026 05:02
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

A new shell function family_tweaks_bsp() is added to the meson-sm1 configuration file. This function creates a systemd shutdown directory and installs an odroid shutdown script with specific permissions when the board is odroidhc4. No existing logic was modified.

Changes

Cohort / File(s) Summary
ODroid HC4 Board Support
config/sources/families/meson-sm1.conf
Added family_tweaks_bsp() function that creates systemd shutdown directory and installs odroid.shutdown script with 0755 permissions for odroidhc4 board variant

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A shutdown script hops in place,
For odroidhc4's special case,
With permissions set just right,
The system shuts down through the night! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: adding HDD park functionality at shutdown for the Odroid HC4.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

Hey @ean365! 👋

Thanks for submitting your first pull request to the Armbian project — we're excited to have you contributing! 🧡
Your effort doesn’t just improve Armbian — it benefits the entire community of users and developers.

If you'd like to stay informed about project updates or collaborate more closely with the team,
you can optionally share some personal contact preferences at armbian.com/update-data.
This helps us keep in touch without relying solely on GitHub notifications.

Also, don’t forget to ⭐ star the repo if you haven’t already — and welcome aboard! 🚀

@github-actions github-actions bot added size/small PR with less then 50 lines 02 Milestone: First quarter release Needs review Seeking for review Hardware Hardware related like kernel, U-Boot, ... labels Jan 10, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
config/sources/families/meson-sm1.conf (1)

20-26: Excellent approach: reusing existing shutdown script for HC4.

The implementation correctly gates HDD parking functionality to the odroidhc4 board and follows proper systemd shutdown hook conventions. The permissions (0755) and ownership (root:root) are appropriate. The family_tweaks_bsp() function is automatically invoked during BSP package creation, $destination is properly initialized in the build context, and the odroid.shutdown script exists and is compatible with HC4's SATA hardware.

Optional: Add defensive quoting for path variables

While the current implementation works correctly, quoting variables is a defensive coding best practice for shell scripts:

Suggested improvement
 family_tweaks_bsp() {
 	if [[ $BOARD == odroidhc4 ]]; then
 		# park HDD heads on shutdown
-		mkdir -p $destination/lib/systemd/system-shutdown
-		install -o root -g root -m 0755 $SRC/packages/bsp/odroid/odroid.shutdown $destination/lib/systemd/system-shutdown/odroid.shutdown
+		mkdir -p "$destination/lib/systemd/system-shutdown"
+		install -o root -g root -m 0755 "$SRC/packages/bsp/odroid/odroid.shutdown" "$destination/lib/systemd/system-shutdown/odroid.shutdown"
 	fi
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cbfbfb6 and 432df82.

📒 Files selected for processing (1)
  • config/sources/families/meson-sm1.conf
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: igorpecovnik
Repo: armbian/build PR: 8849
File: config/boards/radxa-e54c.csc:14-28
Timestamp: 2025-11-02T20:49:56.719Z
Learning: In Armbian board configuration files (config/boards/*.conf, *.csc, etc.), do not use kernel_config_set, kernel_config_set_m, kernel_config_set_y, or custom_kernel_config__* functions to modify kernel configuration. Kernel configuration is associated with LINUXFAMILY/BOARDFAMILY, not individual BOARD. Board-specific kernel modifications cause inconsistency in kernel packages published to the apt repository because boards within a family share the same kernel packages. Kernel configuration changes must be made in the appropriate kernel config file (e.g., config/kernel/linux-*-*.config) or in family configuration files (config/sources/families/*.conf, *.inc) instead.
Learnt from: igorpecovnik
Repo: armbian/build PR: 8720
File: lib/functions/rootfs/distro-specific.sh:38-47
Timestamp: 2025-11-09T22:30:27.163Z
Learning: In lib/functions/rootfs/distro-specific.sh, the systemd sleep.conf.d override that disables suspend/hibernation is intentionally applied system-wide to all Armbian images (desktop, CLI, and minimal), not gated to desktop-only builds, because suspend/resume is fragile on most boards.
📚 Learning: 2025-11-02T20:49:56.719Z
Learnt from: igorpecovnik
Repo: armbian/build PR: 8849
File: config/boards/radxa-e54c.csc:14-28
Timestamp: 2025-11-02T20:49:56.719Z
Learning: In Armbian board configuration files (config/boards/*.conf, *.csc, etc.), do not use kernel_config_set, kernel_config_set_m, kernel_config_set_y, or custom_kernel_config__* functions to modify kernel configuration. Kernel configuration is associated with LINUXFAMILY/BOARDFAMILY, not individual BOARD. Board-specific kernel modifications cause inconsistency in kernel packages published to the apt repository because boards within a family share the same kernel packages. Kernel configuration changes must be made in the appropriate kernel config file (e.g., config/kernel/linux-*-*.config) or in family configuration files (config/sources/families/*.conf, *.inc) instead.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-11-09T22:30:27.163Z
Learnt from: igorpecovnik
Repo: armbian/build PR: 8720
File: lib/functions/rootfs/distro-specific.sh:38-47
Timestamp: 2025-11-09T22:30:27.163Z
Learning: In lib/functions/rootfs/distro-specific.sh, the systemd sleep.conf.d override that disables suspend/hibernation is intentionally applied system-wide to all Armbian images (desktop, CLI, and minimal), not gated to desktop-only builds, because suspend/resume is fragile on most boards.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-09-14T06:32:29.806Z
Learnt from: amazingfate
Repo: armbian/build PR: 8619
File: config/sources/families/rockchip.conf:222-230
Timestamp: 2025-09-14T06:32:29.806Z
Learning: In the Armbian build system, the write_uboot_platform() function implementations follow different patterns across Rockchip family files. The newer standard (used in rockchip64_common.inc and rk3506) includes 'status=none' parameter in dd commands, while older implementations (rk3288, rk322x) use an older pattern without this parameter. The rk3506 implementation correctly follows the current Rockchip family standard.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-07-23T07:30:52.265Z
Learnt from: EvilOlaf
Repo: armbian/build PR: 8417
File: config/boards/orangepi5pro.csc:57-58
Timestamp: 2025-07-23T07:30:52.265Z
Learning: In the Armbian build system, BOOTPATCHDIR can contain board-specific subdirectories (e.g., board_orangepi5pro) for applying patches to specific boards only. The framework automatically checks if such board-specific subdirectories exist for the board being built and applies those patches accordingly.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-09-12T19:28:38.491Z
Learnt from: Grippy98
Repo: armbian/build PR: 8622
File: config/sources/families/k3.conf:66-66
Timestamp: 2025-09-12T19:28:38.491Z
Learning: In the Armbian k3 family build system (config/sources/families/k3.conf), builds do not fail when TIBOOT3_BOOTCONFIG is unset, even though tiboot3.bin is still listed in UBOOT_TARGET_MAP. The gating mechanism in pre_config_uboot_target__build_first_stage function works as intended to conditionally build/copy tiboot3.bin only when TIBOOT3_BOOTCONFIG is defined.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-10-14T05:08:11.785Z
Learnt from: EvilOlaf
Repo: armbian/build PR: 8754
File: config/boards/bestv-r3300-l.csc:14-16
Timestamp: 2025-10-14T05:08:11.785Z
Learning: In the Armbian build system, BOOTBRANCH_BOARD is a valid framework variable used as a fallback when BOOTBRANCH is unset. The framework checks BOOTBRANCH_BOARD before applying the default bootloader branch value (see config/sources/common.conf). Board configuration files can use BOOTBRANCH_BOARD to specify the bootloader branch.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-08-03T15:21:20.148Z
Learnt from: pyavitz
Repo: armbian/build PR: 8455
File: config/sources/families/sun50iw1.conf:19-24
Timestamp: 2025-08-03T15:21:20.148Z
Learning: In the Armbian build system, when copying firmware files during family_tweaks_s(), use /lib/firmware/updates/ instead of /lib/firmware/ to avoid conflicts with the Armbian firmware package. The /lib/firmware/updates directory takes precedence in Linux firmware loading hierarchy and is the proper location for user-installed firmware files.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-12-16T12:22:20.156Z
Learnt from: tabrisnet
Repo: armbian/build PR: 9085
File: lib/functions/rootfs/rootfs-create.sh:303-306
Timestamp: 2025-12-16T12:22:20.156Z
Learning: The post_debootstrap_customize hook concept in lib/functions/rootfs/rootfs-create.sh has been tested in PR #9000. The hook placement after package installations (including desktop packages) and before cleanup operations (autoremove, qemu undeploy) is validated as appropriate for rootfs customization.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-09-07T17:39:32.272Z
Learnt from: EvilOlaf
Repo: armbian/build PR: 8586
File: config/boards/nanopi-r76s.conf:15-21
Timestamp: 2025-09-07T17:39:32.272Z
Learning: In the Armbian build system, the variables $BOARD and $SDCARD are always set by the build framework, so guard checks for these variables are unnecessary in board configuration files and hook functions.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-10-26T10:41:35.118Z
Learnt from: HackingGate
Repo: armbian/build PR: 8665
File: config/boards/photonicat2.csc:4-4
Timestamp: 2025-10-26T10:41:35.118Z
Learning: In the Armbian build system, rk3576 boards consistently use BOARDFAMILY="rk35xx" for both vendor and edge kernel targets. The rk35xx family configuration sources rockchip64_common.inc, which provides edge and current kernel branch definitions, making these branches available even though they're not defined directly in rk35xx.conf.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-12-13T11:39:08.046Z
Learnt from: pyavitz
Repo: armbian/build PR: 9058
File: patch/u-boot/legacy/u-boot-spacemit-k1/003-SpacemiT-K1X-Fixups.patch:28-67
Timestamp: 2025-12-13T11:39:08.046Z
Learning: In the Armbian build system for SpacemiT U-Boot patches (patch/u-boot/legacy/u-boot-spacemit-k1/), alignment with mainline U-Boot behavior is prioritized. For example, in boot mode handling, leaving devnum unchanged in the default case (when devtype is cleared) follows mainline conventions rather than explicitly clearing it to handle edge cases.

Applied to files:

  • config/sources/families/meson-sm1.conf
📚 Learning: 2025-12-12T23:09:56.813Z
Learnt from: tabrisnet
Repo: armbian/build PR: 9058
File: config/sources/families/spacemit.conf:39-45
Timestamp: 2025-12-12T23:09:56.813Z
Learning: In Armbian build configs for vendor kernel sources, prefer the following branch naming conventions: use 'vendor' or 'vendor-rt' for stable vendor releases, and 'vendor-edge' for bleeding-edge/pre-release vendor versions. The 'edge' naming without the 'vendor-' prefix is reserved for mainline kernel branches. Apply this pattern to family config files under config/sources/families (e.g., spacemit.conf) to ensure consistent vendor kernel sourcing naming across the repository.

Applied to files:

  • config/sources/families/meson-sm1.conf

Copy link
Member

@rpardini rpardini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, but please do board-specific stuff in the board file, not the family file.
You can use a hook called post_family_tweaks_bsp.

@igorpecovnik
Copy link
Member

Also perhaps replacing the script with something like this (needs testing):

#!/bin/bash
# Minimal disk quiesce + detach
# Dependencies: bash, sync, sleep, sysfs

set -u
exec </dev/null >/dev/null 2>/dev/null
export LANG=C LC_ALL=C

# Flush all buffers
sync

# Wait for MD arrays to become clean if mdadm exists (optional)
if [ -x /sbin/mdadm ]; then
    /sbin/mdadm --wait-clean --scan || true
elif command -v mdadm >/dev/null 2>&1; then
    mdadm --wait-clean --scan || true
fi

park_disks() {
    local dev base delete

    for dev in /sys/block/sd*; do
        [ -e "$dev" ] || continue
        base="${dev##*/}"
        delete="/sys/class/block/$base/device/delete"

        # Remove disk from kernel (forces final flush + shutdown path)
        if [ -w "$delete" ]; then
            echo 1 >"$delete" || true
        fi
    done
}

case "${1:-}" in
    reboot|kexec)
        # Skip on reboot/kexec
        ;;
    *)
        park_disks
        ;;
esac

@ean365
Copy link
Contributor Author

ean365 commented Jan 10, 2026

Thanks.

@rpardini, are you saying that it should be added to odroidhc4.csc instead? I put it in meson-sm1.conf because I'm not totally familiar with Armbian practice, thought it needed to be in a conf file, and because I already saw multiple device specific stuff in there. I can switch it into the .csc, if that is what you are recommending??

@igorpecovnik, I was trying to reuse the existing odroid.shutdown script, since it is used by the XU4 as well, and it is working. Do you see a big difference between them? If so, and you prefer the new one above, then I can put in a separate PR to update the script as you suggest.

I have been chasing/fixing this issue all night into the wee hours. I'm going to bed, but will check your responses tomorrow (well later today).

@EvilOlaf
Copy link
Member

@rpardini, are you saying that it should be added to odroidhc4.csc instead? I put it in meson-sm1.conf because I'm not totally familiar with Armbian practice, thought it needed to be in a conf file, and because I already saw multiple device specific stuff in there. I can switch it into the .csc, if that is what you are recommending??

Correct. The board config file can also contain hooks to modify aspects. Hooks in the board family file (meson-sm1.conf in this case) would affect all boards depending on this family config (like odroid n2 for example).

@igorpecovnik
Copy link
Member

Do you see a big difference between them?

less dependencies. hdparm is not present in minimal image. If it works on HC4 it will also work on XU4. Also it can be moved to extensions.

@ean365
Copy link
Contributor Author

ean365 commented Jan 10, 2026

Hmmm. I hadn't realized it, but it looks like mdadm is not present in minimal image either. Although, I've just been using JBOD for backups, so it hasn't been an issue for me. Is mdadm definitely installed when running RAID? Maybe the bsp for this feature should install both necessary commands? I'll have to test your code, because the hdparm -y command specifically parks the HDD heads nicely. I'm not sure if skipping that and just deleting from the kernel does that (or I would have assumed this wouldn't be needed in the first place if the heads were parked nicely). But I'll check it out...

It's strange that the kernel driver for the HDD doesn't have this built-in -- total miss on that.

Also, I'm not quite sure what you meant by moving to extensions, you have way more to say in how you want to structure the project, but it seems like it is part of bsp to me, where it has been for XU4.

@rpardini
Copy link
Member

rpardini commented Jan 10, 2026

Hey, it's me again. As you noted, there's two parts to this: one script laying around somewhere, and the board-related code (legacy "tweaks", which is usually family code) that activates it.

"Extensions" are a mechanism where we can define both in a single place (using heredocs, or declare -f tricks) and have boards share it. Take a look at the docs and extensions/ directory plus their usages (grep for enable_extension).

Ref "minimal images don't have xxx": then don't do xxx for minimals. With an extension you can test for BUILD_MINIMAL (or whatever) and complain/warn/break/avoid-it etc.

@igorpecovnik
Copy link
Member

Is mdadm definitely installed when running RAID?

Yes. So the script looks for its existence and only then proceed. mdadm is thus not a dependency and nor require package if you don't intend to have raid setup.

Also, I'm not quite sure what you meant by moving to extensions, you have way more to say in how you want to structure the project, but it seems like it is part of bsp to me, where it has been for XU4.

This is just an idea as parking HDD is not something that is only tied to those boards. Extension is clean(er) way for sorting out those things. As with next board that might need this kind of functionality its only one line of code:

Example:
https://github.com/armbian/build/blob/main/config/boards/luckfox-lyra-ultra-w.csc#L11
https://github.com/armbian/build/blob/main/extensions/radxa-aic8800.sh

IMO if this part is getting attention, lets do it better?

@iav
Copy link
Contributor

iav commented Jan 12, 2026

Such an improvement would benefit both official NAS — Helios4, Helios64, ODroidHC4, and other pets that use USB rotational drives. Will it work for USB drives?

@ean365
Copy link
Contributor Author

ean365 commented Jan 13, 2026

Such an improvement would benefit both official NAS — Helios4, Helios64, ODroidHC4, and other pets that use USB rotational drives. Will it work for USB drive's.

If by "USB drives" you mean USB connected HDDs, then yes I think it should work just the same to command them to park the heads (idle-immediate), but of course this implementation would need to be tested to be certain.

@rpardini
Copy link
Member

If by "USB drive's" you mean USB connected HDDs, then yes I think it should work just the same to command them to park the heads (idle-immediate),

There's only so much one can do as what SATA command is emitted by the USB controller is up to the USB controller. But yes, one can send hdparm -y to any /dev/sdX disk, if it works or not is a different matter.

@rpardini
Copy link
Member

As part of testing the other PR, I reworked the odroid.shutdown script into some sata.shutdown. Maybe this could fit into an extension that's generic enough? It checks for existence of things (doesn't fail if mdadm or hdparm missing, etc) and emits logs to kernel log (which is the only thing available that late in the game); also avoids removing the device from kernel if it is still mounted, which is the case when rootfs is on a SATA disk. (It still parks the heads, though).

#!/bin/bash
set -euo pipefail

declare LOG_PREFIX="sata.shutdown"
declare -i DRY_RUN=0

log_kmsg() {
	declare msg="$1"
	echo "${LOG_PREFIX}: ${msg}" > /dev/kmsg
}

sleep_and_log() {
	declare -i wait="$1"
	if ((wait > 0)); then
		log_kmsg "Sleeping for ${wait} seconds."
	fi
	((DRY_RUN)) || sleep "${wait}"
	return 0
}

[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=1 && log_kmsg "Dry run mode enabled."
log_kmsg "Starting shutdown script (DRY_RUN==${DRY_RUN})."

export LANG=C LC_ALL=C

log_kmsg "Syncing filesystems."
((DRY_RUN)) && log_kmsg "[dry-run] Would sync filesystems." || sync || log_kmsg "Warning: sync failed."

if command -v mdadm &> /dev/null; then
	log_kmsg "Waiting for mdadm arrays to clean."
	((DRY_RUN)) && log_kmsg "[dry-run] Would run: mdadm --wait-clean --scan." || mdadm --wait-clean --scan || log_kmsg "Warning: mdadm --wait-clean failed."
else
	log_kmsg "mdadm not found, skipping RAID clean."
fi

if ! command -v hdparm &> /dev/null; then
	log_kmsg "hdparm not found, skipping disk parking."
	exit 1
fi

log_kmsg "Parking SATA disks."
for dev in /sys/block/sd*; do
	declare -i wait=0
	if [[ ! -e "${dev}" ]]; then
		log_kmsg "Could not find ${dev}, skipping."
		continue
	fi
	declare dev_name="${dev##*/}"
	declare dev_path="/dev/${dev_name}"

	declare mounted=0 # Check if device or any partition is mounted using findmnt
	if findmnt -rn -S "${dev_path}" > /dev/null; then
		log_kmsg "Device ${dev_path} is mounted (findmnt). Will skip kernel delete."
		mounted=1
	else
		for part in "/dev/${dev_name}"[0-9]*; do
			[[ -b "${part}" ]] || continue
			if findmnt -rn -S "${part}" > /dev/null; then
				log_kmsg "Partition ${part} is mounted (findmnt). Will skip kernel delete for ${dev_path}."
				mounted=1
				break
			fi
		done
	fi

	if ((DRY_RUN)); then
		log_kmsg "[dry-run] Would run: hdparm -y ${dev_path}"
		log_kmsg "[dry-run] Would set wait=2."
		wait=2
	else
		if hdparm -y "${dev_path}"; then
			log_kmsg "Disk ${dev_path} parked."
			wait=2
		else
			log_kmsg "Warning: hdparm failed for ${dev_path}"
		fi
	fi

	if ((mounted)); then
		log_kmsg "Mounted filesystems detected on ${dev_path} or its partitions. Skipping kernel delete."
	elif ((DRY_RUN)); then
		log_kmsg "[dry-run] Would run: echo 1 > /sys/class/block/${dev_name}/device/delete"
		log_kmsg "[dry-run] Would log: Disk ${dev_path} deleted from kernel."
	else
		sleep_and_log "${wait}"

		log_kmsg "Deleting disk ${dev_path} from kernel /sys/class/block/${dev_name}/device/delete ..."
		if echo 1 > "/sys/class/block/${dev_name}/device/delete"; then
			log_kmsg "Disk ${dev_path} deleted from kernel."
		else
			log_kmsg "Warning: failed to delete ${dev_path} from kernel."
		fi
	fi
	sleep_and_log "${wait}"
done

log_kmsg "Shutdown script completed."
exit 0

This produces serial console output similar to

[  OK  ] Reached target reboot.target - System Reboot.
[ 1948.195032] watchdog: watchdog0: watchdog did not stop!
[ 1948.229030] systemd-shutdown[1]: Using hardware watchdog 'Meson GXBB Watchdog', version 0, device /dev/watchdog0
[ 1948.233695] systemd-shutdown[1]: Watchdog running with a hardware timeout of 10min.
[ 1948.248691] systemd-shutdown[1]: Syncing filesystems and block devices.
[ 1948.471608] systemd-shutdown[1]: Sending SIGTERM to remaining processes...
[ 1948.493137] systemd-journald[1188]: Received SIGTERM from PID 1 (systemd-shutdow).
[ 1948.672782] systemd-shutdown[1]: Sending SIGKILL to remaining processes...
[ 1948.692666] systemd-shutdown[1]: Unmounting file systems.
[ 1948.694490] (sd-umount)[3894]: Unmounting '/run/credentials/systemd-journald.service'.
[ 1948.702622] (sd-remount)[3895]: Remounting '/' read-only with options 'errors=remount-ro,commit=120'.
[ 1948.809720] EXT4-fs (sda1): re-mounted 5ebb1b15-8f47-46a5-9792-9104678616a8 ro.
[ 1948.831002] systemd-shutdown[1]: All filesystems unmounted.
[ 1948.831047] systemd-shutdown[1]: Deactivating swaps.
[ 1948.835962] systemd-shutdown[1]: All swaps deactivated.
[ 1948.842255] systemd-shutdown[1]: Detaching loop devices.
[ 1948.852825] systemd-shutdown[1]: All loop devices detached.
[ 1948.852868] systemd-shutdown[1]: Stopping MD devices.
[ 1948.858096] systemd-shutdown[1]: All MD devices stopped.
[ 1948.863105] systemd-shutdown[1]: Detaching DM devices.
[ 1948.868475] systemd-shutdown[1]: All DM devices detached.
[ 1948.873583] systemd-shutdown[1]: All filesystems, swaps, loop devices, MD devices and DM devices detached.
[ 1948.883200] watchdog: watchdog0: watchdog did not stop!
[ 1948.980540] sata.shutdown: Starting shutdown script (DRY_RUN==0).
[ 1948.981268] sata.shutdown: Syncing filesystems.
[ 1948.997462] sata.shutdown: Waiting for mdadm arrays to clean.
[ 1949.012263] sata.shutdown: Parking SATA disks.
[ 1949.049277] sata.shutdown: Partition /dev/sda1 is mounted (findmnt). Will skip kernel delete for /dev/sda.
[ 1949.621692] sata.shutdown: Disk /dev/sda parked.
[ 1949.622007] sata.shutdown: Mounted filesystems detected on /dev/sda or its partitions. Skipping kernel delete.
[ 1949.632305] sata.shutdown: Sleeping for 2 seconds.
[ 1952.303345] sata.shutdown: Shutdown script completed.
[ 1952.306632] systemd-shutdown[1]: Syncing filesystems and block devices.
[ 1952.309581] systemd-shutdown[1]: Rebooting.
[ 1952.357496] sd 1:0:0:0: [sda] Synchronizing SCSI cache
[ 1952.382081] kvm: exiting hardware virtualization
[ 1952.382130] reboot: Restarting system

@iav
Copy link
Contributor

iav commented Jan 20, 2026

It's so strange to learn about the need to park disk heads, when the last time I saw this was before the Windows '95 era... Although back then it was common knowledge, and everyone always gave the command to park disks before turning off their PCs.
Greetings from the past, which I have long forgotten.

@ean365
Copy link
Contributor Author

ean365 commented Jan 20, 2026

I remember. Old HDDs in 80s-early-90s absolutely needed to be parked. Later, the automatic/emergency parking feature was added using stored energy. Even though, in modern times, it works fairly reliably and has been optimized, it is still nonetheless considered an emergency, somewhat "violent", and something to be avoided -- give it a warning and time to do it nicely, if possible...

Linux ATA driver (and most modern OSs) still does send standby-immediate to programmatically park HDDs during shutdown.
https://github.com/torvalds/linux/blob/24d479d26b25bce5faea3ddd9fa8f3a6c3129ea7/drivers/ata/libata-core.c#L2039-L2048

So, I think the issue in this case might just be that after sending the standby-immediate, the driver doesn't wait and give it time to do so before cutting power. (Gigahertz CPUs vs mechanical mechanism) (Not sure, but maybe another opportunity to fix an obscure "bug" in a Linux driver??? Would need to do some deeper debugging to be sure.)

UPDATE: Actually, it is starting to look like Linux ATA driver only sends a standby-immediate to the HDD if it is actually shutting down, but not if it is restarting. That seems like an oversight since many machines will power-cycle during a restart. I just tested my HC4 by first removing the system-shutdown/odroid.shutdown script and then issuing a shutdown now instead of restarting it, and after cycling its power, the smartctl showed that the HDD did not increment Power-off_Retract_Count -- did not incur an emergency head retract. So, I assume that it was sent a standby-immediate by the kernel ATA driver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

02 Milestone: First quarter release Hardware Hardware related like kernel, U-Boot, ... Needs review Seeking for review size/small PR with less then 50 lines

Development

Successfully merging this pull request may close these issues.

5 participants