Re: [PATCH 00/38] docs: several improvements to kernel-doc

Randy Dunlap
2026-02-22T01:24:21Z

[-- Attachment #1: Type: text/plain, Size: 1421 bytes --]

On 2/20/26 5:24 PM, Randy Dunlap wrote:
>
>
> On 2/18/26 2:12 AM, Mauro Carvalho Chehab wrote:
>> Hi Jon,
>>
>> This series contain several improvements for kernel-doc.
>>
>> Most of the patches came from v4 of this series:
>> https://lore.kernel.org/linux-doc/cover.1769867953.git.mchehab+huawei@kernel.org/
>>
>
> Mauro,
> Is this series available as a git tree/branch?
>
> Or what is the base for applying this series?

I applied the series to linux-next-20260220. It applies cleanly
except for one gotcha (using 'patch'):

In patch 25, in the commit description, I had to change the
example before/after diff to have leading "//" ('patch' was
treating them as part of the diff).

I am still seeing kernel-doc warnings being duplicated.
Seems like there a patch for that but it's not applied yet and not part
of this series...?

The results on linux-next-20260220 look good.
I do have one issue on a test file that I had sent to you (Mauro)
earlier: kdoc-nested.c

In struct super_struct, the fields of nested struct tlv are not
described but there is no warning about that.
Likewise for the fields of the nested structs header, gen_descr,
and data.

Does this series address when /* private: */ is turned off at the
end of a struct/union? If so, I don't see it working.
See struct nla_policy for where the final struct member should be
public.

kdoc-nested.c test file is attached.

thanks.
--
~Randy

[-- Attachment #2: kdoc-nested.c --]
[-- Type: text/x-csrc, Size: 18826 bytes --]

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/module.h>

// from Documentation/doc-guide/kernel-doc.rst:

/**
* struct nested_foobar - a struct with nested unions and structs
* @memb1: first member of anonymous union/anonymous struct
* @memb2: second member of anonymous union/anonymous struct
* @memb3: third member of anonymous union/anonymous struct
* @memb4: fourth member of anonymous union/anonymous struct
* @bar: non-anonymous union
* @bar.st1: struct st1 inside @bar
* @bar.st2: struct st2 inside @bar
* @bar.st1.memb1: first member of struct st1 on union bar
* @bar.st1.memb2: second member of struct st1 on union bar
* @bar.st2.memb1: first member of struct st2 on union bar
* @bar.st2.memb2: second member of struct st2 on union bar
*/
struct nested_foobar {
/* Anonymous union/struct*/
union {
struct {
int memb1;
int memb2;
};
struct {
void *memb3;
int memb4;
};
};
union {
struct {
int memb1;
int memb2;
} st1;
struct {
void *memb1;
int memb2;
} st2;
} bar;
};

/*
* struct knav_dma_tx_cfg: Tx channel configuration
* @filt_einfo: Filter extended packet info
* @filt_pswords: Filter PS words present
* @priority: Tx channel scheduling priority
*/
struct knav_dma_tx_cfg {
bool filt_einfo;
bool filt_pswords;
u32 priority;
};

#define KNAV_DMA_FDQ_PER_CHAN 4
/*
* struct knav_dma_rx_cfg: Rx flow configuration
* @einfo_present: Extended packet info present
* @psinfo_present: PS words present
* @err_mode: Error during buffer starvation
* @desc_type: Host or Monolithic desc
* @psinfo_at_sop: PS word located at start of packet
* @sop_offset: Start of packet offset
* @dst_q: Destination queue for a given flow
* @thresh: Rx flow size threshold
* @fdq: Free desc Queue array
* @sz_thresh0: RX packet size threshold 0
* @sz_thresh1: RX packet size threshold 1
* @sz_thresh2: RX packet size threshold 2
*/
struct knav_dma_rx_cfg {
bool einfo_present;
bool psinfo_present;
u32 err_mode;
u32 desc_type;
bool psinfo_at_sop;
unsigned int sop_offset;
unsigned int dst_q;
u32 thresh;
unsigned int fdq[KNAV_DMA_FDQ_PER_CHAN];
unsigned int sz_thresh0;
unsigned int sz_thresh1;
unsigned int sz_thresh2;
};

// from include/linux/soc/ti/knav_dma.h: struct knav_dma_cfg (w/ 2 warnings)
/**
* struct knav_dma_cfg: Pktdma channel configuration
* @u.sl_cfg: Slave configuration
* @u.tx: Tx channel configuration
* @u.rx: Rx flow configuration
*/
struct knav_dma_cfg {
u32 direction;
union {
struct knav_dma_tx_cfg tx;
struct knav_dma_rx_cfg rx;
} u;
};
// Above Fixed: (no warnings)
/**
* struct knav_dma_cfg2: Pktdma channel configuration
* @direction: DMA direction info
* @u: @tx or @rx configuration
* @u.tx: Tx channel configuration
* @u.rx: Rx flow configuration
*/
struct knav_dma_cfg2 {
u32 direction;
union {
struct knav_dma_tx_cfg tx;
struct knav_dma_rx_cfg rx;
} u;
};

enum dev_prop_type {
DEV_PROP_U8,
DEV_PROP_U16,
DEV_PROP_U32,
DEV_PROP_U64,
DEV_PROP_STRING,
DEV_PROP_REF,
};

// from include/linux/property.h: struct property_entry (w/ 5 warnings)
/**
* struct property_entry - "Built-in" device property representation.
* @name: Name of the property.
* @length: Length of data making up the value.
* @is_inline: True when the property value is stored inline.
* @type: Type of the data in unions.
* @pointer: Pointer to the property when it is not stored inline.
* @value: Value of the property when it is stored inline.
*/
struct property_entry {
const char *name;
size_t length;
bool is_inline;
enum dev_prop_type type;
union {
const void *pointer;
union {
u8 u8_data[sizeof(u64) / sizeof(u8)];
u16 u16_data[sizeof(u64) / sizeof(u16)];
u32 u32_data[sizeof(u64) / sizeof(u32)];
u64 u64_data[sizeof(u64) / sizeof(u64)];
const char *str[sizeof(u64) / sizeof(char *)];
} value;
};
};
// Above, Modified (partially fixed due to confusion about requirements)
/**
* struct property_entry2 - "Built-in" device property representation.
* @name: Name of the property.
* @length: Length of data making up the value.
* @is_inline: True when the property value is stored inline.
* @type: Type of the data in unions.
* @pointer: Pointer to the property when it is not stored inline.
* @value: Value of the property when it is stored inline.
* @u8_data: @value as an array of u8 numbers
* @u16_data: @value as an array of u16 numbers
* @u32_data: @value as an array of u32 numbers
* @u64_data: @value as an array of u64 numbers
* @str: @value as an array of char pointers
*/
struct property_entry2 {
const char *name;
size_t length;
bool is_inline;
enum dev_prop_type type;
union {
const void *pointer;
union {
u8 u8_data[sizeof(u64) / sizeof(u8)];
u16 u16_data[sizeof(u64) / sizeof(u16)];
u32 u32_data[sizeof(u64) / sizeof(u32)];
u64 u64_data[sizeof(u64) / sizeof(u64)];
const char *str[sizeof(u64) / sizeof(char *)];
} value;
};
};

# define NUM_MSI_ALLOC_SCRATCHPAD_REGS 2

// from include/asm-generic/msi.h: struct msi_alloc_info (added: @flags)
// Should there be any warnings when @ul and @ptr are omitted?
/**
* struct msi_alloc_info - Default structure for MSI interrupt allocation.
* @desc: Pointer to msi descriptor
* @hwirq: Associated hw interrupt number in the domain
* @flags: Bits from MSI_ALLOC_FLAGS_...
* @scratchpad: Storage for implementation specific scratch data
* @scratchpad.ul: Scratchpad data as unsigned long
* @scratchpad.ptr: Scratchpad data as a pointer
*
* Architectures can provide their own implementation by not including
* asm-generic/msi.h into their arch specific header file.
*/
typedef struct msi_alloc_info {
struct msi_desc *desc;
irq_hw_number_t hwirq;
unsigned long flags;
union {
unsigned long ul;
void *ptr;
} scratchpad[NUM_MSI_ALLOC_SCRATCHPAD_REGS];
} msi_alloc_info_t;

#define NAND_MAX_ID_LEN 8
// from include/linux/mtd/rawnand.h: struct nand_flash_dev[ice] (2 versions)
// WITHOUT named nested unions/structs:
/**
* struct nand_flash_dev - NAND Flash Device ID Structure
* @name: a human-readable name of the NAND chip
* @dev_id: the device ID (the second byte of the full chip ID array)
* @mfr_id: manufacturer ID part of the full chip ID array (refers the same
* memory address as ``id[0]``)
* @dev_id: device ID part of the full chip ID array (refers the same memory
* address as ``id[1]``)
* @id: full device ID array
* @pagesize: size of the NAND page in bytes; if 0, then the real page size (as
* well as the eraseblock size) is determined from the extended NAND
* chip ID array)
* @chipsize: total chip size in MiB
* @erasesize: eraseblock size in bytes (determined from the extended ID if 0)
* @options: stores various chip bit options
* @id_len: The valid length of the @id.
* @oobsize: OOB size
* @ecc: ECC correctability and step information from the datasheet.
* @ecc.strength_ds: The ECC correctability from the datasheet, same as the
* @ecc_strength_ds in nand_chip{}.
* @ecc.step_ds: The ECC step required by the @ecc.strength_ds, same as the
* @ecc_step_ds in nand_chip{}, also from the datasheet.
* For example, the "4bit ECC for each 512Byte" can be set with
* NAND_ECC_INFO(4, 512).
*/
struct nand_flash_dev {
char *name;
union {
struct {
uint8_t mfr_id;
uint8_t dev_id;
};
uint8_t id[NAND_MAX_ID_LEN];
};
unsigned int pagesize;
unsigned int chipsize;
unsigned int erasesize;
unsigned int options;
uint16_t id_len;
uint16_t oobsize;
struct {
uint16_t strength_ds;
uint16_t step_ds;
} ecc;
};
// WITH named nested unions/structs:
/**
* struct nand_flash_device - NAND Flash Device ID Structure
* @name: a human-readable name of the NAND chip
* @ids_ary: union containing all nand id info
* @ids_ary.ids: nand ids (@dev_id, @mfr_id)
* @ids_ary.ids.mfr_id: manufacturer ID part of the full chip ID array
* (refers to the same memory address as ``id[0]``)
* @ids_ary.ids.dev_id: device ID part of the full chip ID array (refers to the
* same memory address as ``id[1]``)
* @ids_ary.id: full device ID array
* @pagesize: size of the NAND page in bytes; if 0, then the real page size (as
* well as the eraseblock size) is determined from the extended NAND
* chip ID array)
* @chipsize: total chip size in MiB
* @erasesize: eraseblock size in bytes (determined from the extended ID if 0)
* @options: stores various chip bit options
* @id_len: The valid length of the @id.
* @oobsize: OOB size
* @ecc: ECC correctability and step information from the datasheet.
* @ecc.strength_ds: The ECC correctability from the datasheet, same as the
* @ecc_strength_ds in nand_chip{}.
* @ecc.step_ds: The ECC step required by the @ecc.strength_ds, same as the
* @ecc_step_ds in nand_chip{}, also from the datasheet.
* For example, the "4bit ECC for each 512Byte" can be set with
* NAND_ECC_INFO(4, 512).
*/
struct nand_flash_device {
char *name;
union u_ids_ary {
struct nand_ids {
uint8_t mfr_id;
uint8_t dev_id;
} ids;
uint8_t id[NAND_MAX_ID_LEN];
} ids_ary;
unsigned int pagesize;
unsigned int chipsize;
unsigned int erasesize;
unsigned int options;
uint16_t id_len;
uint16_t oobsize;
struct ecc_ds {
uint16_t strength_ds;
uint16_t step_ds;
} ecc;
};

// from include/linux/spi/spi-mem.h: struct spi_mem_op (unpatched; 2 warnings)
/**
* struct spi_mem_op - describes a SPI memory operation
* @cmd: the command structure
* @cmd.nbytes: number of opcode bytes (only 1 or 2 are valid). The opcode is
* sent MSB-first.
* @cmd.buswidth: number of IO lines used to transmit the command
* @cmd.opcode: operation opcode
* @cmd.dtr: whether the command opcode should be sent in DTR mode or not
* @addr: address information
* @addr.nbytes: number of address bytes to send. Can be zero if the operation
* does not need to send an address
* @addr.buswidth: number of IO lines used to transmit the address cycles
* @addr.dtr: whether the address should be sent in DTR mode or not
* @addr.val: address value. This value is always sent MSB first on the bus.
* Note that only @addr.nbytes are taken into account in this
* address value, so users should make sure the value fits in the
* assigned number of bytes.
* @dummy: dummy data information
* @dummy.nbytes: number of dummy bytes to send after an opcode or address. Can
* be zero if the operation does not require dummy bytes
* @dummy.buswidth: number of IO lanes used to transmit the dummy bytes
* @dummy.dtr: whether the dummy bytes should be sent in DTR mode or not
* @data: data information
* @data.buswidth: number of IO lanes used to send/receive the data
* @data.dtr: whether the data should be sent in DTR mode or not
* @data.ecc: whether error correction is required or not
* @data.swap16: whether the byte order of 16-bit words is swapped when read
* or written in Octal DTR mode compared to STR mode.
* @data.dir: direction of the transfer
* @data.nbytes: number of data bytes to send/receive. Can be zero if the
* operation does not involve transferring data
* @data.buf.in: input buffer (must be DMA-able)
* @data.buf.out: output buffer (must be DMA-able)
* @max_freq: frequency limitation wrt this operation. 0 means there is no
* specific constraint and the highest achievable frequency can be
* attempted.
*/
struct spi_mem_op {
struct {
u8 nbytes;
u8 buswidth;
u8 dtr : 1;
u8 __pad : 7;
u16 opcode;
} cmd;

struct {
u8 nbytes;
u8 buswidth;
u8 dtr : 1;
u8 __pad : 7;
u64 val;
} addr;

struct {
u8 nbytes;
u8 buswidth;
u8 dtr : 1;
u8 __pad : 7;
} dummy;

struct {
u8 buswidth;
u8 dtr : 1;
u8 ecc : 1;
u8 swap16 : 1;
u8 __pad : 5;
u8 dir;
unsigned int nbytes;
union {
void *in;
const void *out;
} buf;
} data;

unsigned int max_freq;
};

// from include/soc/fsl/dpaa2-fd.h: struct dpaa2_fd (@simple not described)
/**
* struct dpaa2_fd - Struct describing FDs
* @words: for easier/faster copying the whole FD structure
* @simple: simple frame descriptor
* @simple.addr: address in the FD
* @simple.len: length in the FD
* @simple.bpid: buffer pool ID
* @simple.format_offset: format, offset, and short-length fields
* @simple.frc: frame context
* @simple.ctrl: control bits...including dd, sc, va, err, etc
* @simple.flc: flow context address
*
* This structure represents the basic Frame Descriptor used in the system.
*/
struct dpaa2_fd {
union {
u32 words[8];
struct dpaa2_fd_simple {
__le64 addr;
__le32 len;
__le16 bpid;
__le16 format_offset;
__le32 frc;
__le32 ctrl;
__le64 flc;
} simple;
};
};

// from include/net/netlink.h:
struct nlattr;
struct netlink_ext_ack;

/**
* struct nla_policy - attribute validation policy
* @type: Type of attribute or NLA_UNSPEC
* @validation_type: type of attribute validation done in addition to
* type-specific validation (e.g. range, function call), see
* &enum nla_policy_validation
* @len: Type specific length of payload
* @strict_start_type: first attribute to validate strictly
* @pflags: policy flags (should be public:)
*/
struct nla_policy {
u8 type;
u8 validation_type;
u16 len;
union {
u16 strict_start_type;

/* private: use NLA_POLICY_*() to set */
const u32 bitfield32_valid;
const u32 mask;
const char *reject_message;
const struct nla_policy *nested_policy;
const struct netlink_range_validation *range;
const struct netlink_range_validation_signed *range_signed;
struct {
s16 min, max;
};
int (*validate)(const struct nlattr *attr,
struct netlink_ext_ack *extack);
};
/* Should be public; */
u32 pflags;
};

// Contrived nested struct/union tests:

/**
* struct super_struct - nested structs and unions
* @super_type_flags: type of data in this struct plus some common flags
* @uhdr: union for @tlv or @hdr
* @uhdr.tlv: type-length-value descriptor
* @uhdr.hdr: common command/reply header
* @udata: union for @descr or @data
* @udata.descr: generic super descriptor
* @udata.data: flags + pointer or inline data of various basic types
*/
struct super_struct {
u64 super_type_flags;

union ss_header {
struct tlv {
unsigned long type;
unsigned long length;
void *value;
} tlv;

struct header {
unsigned int type;
unsigned int length;
unsigned long flags;
unsigned char req_rep[16];
unsigned char data[];
} hdr;
} uhdr;

union ss_data {
struct gen_descr { // phys_addr, virt_addr, io_addr, dma_addr, io_addr
unsigned int flags; // which addr type
unsigned int addr_flags; // addr-specific flags
u64 base_addr;
u64 end_addr;
unsigned int align;
unsigned int offset;
u64 length;
u64 handle;
void *func;
unsigned char *name;
void *data; // arbitrary data
} descr;

struct data {
u64 dflags; // tells what is in @fun
union actual {
void *pointer;
union values { // inline data
u8 u8_data[sizeof(u64) / sizeof(u8)];
u16 u16_data[sizeof(u64) / sizeof(u16)];
u32 u32_data[sizeof(u64) / sizeof(u32)];
u64 u64_data[sizeof(u64) / sizeof(u64)];
char str[sizeof(u64) / sizeof(char *)];
} value;
} actual;
} data;
} udata;
};

struct part_name {
void *name;
unsigned long name_len;
};

union part_model {
char *inv_model;
char *mfr_model;
} partmodel;

/**
* struct inv_union - nested unions and structs for inventory
*
* (use inline kernel-doc comments here)
*/
union inv_union {
/** @invpart: all local inventory info for a part */
struct inv_part {
/**
* @invpart.type_flags: type of data in this union plus
* some common flags
*/
u64 type_flags;

/** @invpart.part_price: the item's usual selling price */
u64 part_price;

/** @invpart.inv_partnr: the local inventory part number */
char *inv_partnr;

/** @invpart.inv_count: number in inventory */
u64 inv_count;

/** @invpart.partname: local part name (descriptor) */
struct part_name partname;

/** @invpart.partmodel: local part model (pointer) */
union part_model partmodel;
} invpart;

/** @mfrpart: all mfr. info for a part */
struct mfr_part {
/**
* @mfrpart.type_flags: type of data in this union plus
* some common flags
*/
u64 type_flags;

/** @mfrpart.part_price: the item's mfr's cost */
u64 part_price;

/**
* @mfrpart.mfr_partnr: the manufacturer part number
* (descriptor)
*/
char *mfr_partnr;

/** @mfrpart.partname: mfr. part name (descriptor) */
struct part_name partname;

/** @mfrpart.partmodel: mfr. part model (pointer) */
union part_model partmodel;
} mfrpart;
};

// from n2310.pdf - ISO/IEC 9899:202x (E);
// or n1570.pdf - ISO/IEC 9899:201x
// or n1124.pdf - ISO/IEC 9899:TC2
// or ISO/IEC 9899:1999 (E)
/**
* union u - union of all data basic data types
* @n: integer type struct
* @n.alltypes: arbitrary value
* @ni: integer type & value struct
* @ni.type: the type of the value
* @ni.intnode: the int's value
* @nf: double type and value struct
* @nf.type: the type of the value
* @nf.doublenode: the double's value
*/
union u {
struct {
int alltypes;
} n;
struct {
int type;
int intnode;
} ni;
struct {
int type;
double doublenode;
} nf;
} u;

// from n2310.pdf - ISO/IEC 9899:202x (E)
// or n1570.pdf - ISO/IEC 9899:201x
// or n1124.pdf - ISO/IEC 9899:TC2
// or ISO/IEC 9899:1999 (E)
struct s { double i; };

/**
* union g - composite of (int + struct) or (struct + int)
*
* (use inline kernel-doc comments here)
*/
union g {
/** @u1: struct with members in int/struct order */
struct {
int f1;
struct s f2;
} u1;
/** @u2: struct with members in struct/int order */
struct {
struct s f3;
int f4;
} u2;
} g;

// from n1570.pdf - ISO/IEC 9899:201x
/** struct v - anonymous struct & union example
* @i: number i
* @j: number j
* @w: anon. &struct w
* @w.k: number w.k
* @w.l: number w.l
* @m: number m
*/
struct v {
union { // anonymous union
struct { int i, j; }; // anonymous structure
struct { long k, l; } w;
};
int m;
} v1;

#ifdef __KERNEL__

static int __init init_data(void)
{
return -ENODATA;
}
static void __exit exit_data(void)
{
}
module_init(init_data);
module_exit(exit_data);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Randy Dunlap");
MODULE_DESCRIPTION("Nested structs/unions test");
#endif

//EOF//