| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | 
|---|
| 2 | /* | 
|---|
| 3 | * Detect Hung Task: detecting tasks stuck in D state | 
|---|
| 4 | * | 
|---|
| 5 | * Copyright (C) 2025 Tongcheng Travel (www.ly.com) | 
|---|
| 6 | * Author: Lance Yang <mingzhe.yang@ly.com> | 
|---|
| 7 | */ | 
|---|
| 8 | #ifndef __LINUX_HUNG_TASK_H | 
|---|
| 9 | #define __LINUX_HUNG_TASK_H | 
|---|
| 10 |  | 
|---|
| 11 | #include <linux/bug.h> | 
|---|
| 12 | #include <linux/sched.h> | 
|---|
| 13 | #include <linux/compiler.h> | 
|---|
| 14 |  | 
|---|
| 15 | /* | 
|---|
| 16 | * @blocker: Combines lock address and blocking type. | 
|---|
| 17 | * | 
|---|
| 18 | * Since lock pointers are at least 4-byte aligned(32-bit) or 8-byte | 
|---|
| 19 | * aligned(64-bit). This leaves the 2 least bits (LSBs) of the pointer | 
|---|
| 20 | * always zero. So we can use these bits to encode the specific blocking | 
|---|
| 21 | * type. | 
|---|
| 22 | * | 
|---|
| 23 | * Type encoding: | 
|---|
| 24 | * 00 - Blocked on mutex			(BLOCKER_TYPE_MUTEX) | 
|---|
| 25 | * 01 - Blocked on semaphore			(BLOCKER_TYPE_SEM) | 
|---|
| 26 | * 10 - Blocked on rw-semaphore as READER	(BLOCKER_TYPE_RWSEM_READER) | 
|---|
| 27 | * 11 - Blocked on rw-semaphore as WRITER	(BLOCKER_TYPE_RWSEM_WRITER) | 
|---|
| 28 | */ | 
|---|
| 29 | #define BLOCKER_TYPE_MUTEX		0x00UL | 
|---|
| 30 | #define BLOCKER_TYPE_SEM		0x01UL | 
|---|
| 31 | #define BLOCKER_TYPE_RWSEM_READER	0x02UL | 
|---|
| 32 | #define BLOCKER_TYPE_RWSEM_WRITER	0x03UL | 
|---|
| 33 |  | 
|---|
| 34 | #define BLOCKER_TYPE_MASK		0x03UL | 
|---|
| 35 |  | 
|---|
| 36 | #ifdef CONFIG_DETECT_HUNG_TASK_BLOCKER | 
|---|
| 37 | static inline void hung_task_set_blocker(void *lock, unsigned long type) | 
|---|
| 38 | { | 
|---|
| 39 | unsigned long lock_ptr = (unsigned long)lock; | 
|---|
| 40 |  | 
|---|
| 41 | WARN_ON_ONCE(!lock_ptr); | 
|---|
| 42 | WARN_ON_ONCE(READ_ONCE(current->blocker)); | 
|---|
| 43 |  | 
|---|
| 44 | /* | 
|---|
| 45 | * If the lock pointer matches the BLOCKER_TYPE_MASK, return | 
|---|
| 46 | * without writing anything. | 
|---|
| 47 | */ | 
|---|
| 48 | if (WARN_ON_ONCE(lock_ptr & BLOCKER_TYPE_MASK)) | 
|---|
| 49 | return; | 
|---|
| 50 |  | 
|---|
| 51 | WRITE_ONCE(current->blocker, lock_ptr | type); | 
|---|
| 52 | } | 
|---|
| 53 |  | 
|---|
| 54 | static inline void hung_task_clear_blocker(void) | 
|---|
| 55 | { | 
|---|
| 56 | WARN_ON_ONCE(!READ_ONCE(current->blocker)); | 
|---|
| 57 |  | 
|---|
| 58 | WRITE_ONCE(current->blocker, 0UL); | 
|---|
| 59 | } | 
|---|
| 60 |  | 
|---|
| 61 | /* | 
|---|
| 62 | * hung_task_get_blocker_type - Extracts blocker type from encoded blocker | 
|---|
| 63 | * address. | 
|---|
| 64 | * | 
|---|
| 65 | * @blocker: Blocker pointer with encoded type (via LSB bits) | 
|---|
| 66 | * | 
|---|
| 67 | * Returns: BLOCKER_TYPE_MUTEX, BLOCKER_TYPE_SEM, etc. | 
|---|
| 68 | */ | 
|---|
| 69 | static inline unsigned long hung_task_get_blocker_type(unsigned long blocker) | 
|---|
| 70 | { | 
|---|
| 71 | WARN_ON_ONCE(!blocker); | 
|---|
| 72 |  | 
|---|
| 73 | return blocker & BLOCKER_TYPE_MASK; | 
|---|
| 74 | } | 
|---|
| 75 |  | 
|---|
| 76 | static inline void *hung_task_blocker_to_lock(unsigned long blocker) | 
|---|
| 77 | { | 
|---|
| 78 | WARN_ON_ONCE(!blocker); | 
|---|
| 79 |  | 
|---|
| 80 | return (void *)(blocker & ~BLOCKER_TYPE_MASK); | 
|---|
| 81 | } | 
|---|
| 82 | #else | 
|---|
| 83 | static inline void hung_task_set_blocker(void *lock, unsigned long type) | 
|---|
| 84 | { | 
|---|
| 85 | } | 
|---|
| 86 | static inline void hung_task_clear_blocker(void) | 
|---|
| 87 | { | 
|---|
| 88 | } | 
|---|
| 89 | static inline unsigned long hung_task_get_blocker_type(unsigned long blocker) | 
|---|
| 90 | { | 
|---|
| 91 | return 0UL; | 
|---|
| 92 | } | 
|---|
| 93 | static inline void *hung_task_blocker_to_lock(unsigned long blocker) | 
|---|
| 94 | { | 
|---|
| 95 | return NULL; | 
|---|
| 96 | } | 
|---|
| 97 | #endif | 
|---|
| 98 |  | 
|---|
| 99 | #endif /* __LINUX_HUNG_TASK_H */ | 
|---|
| 100 |  | 
|---|