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/[email protected]/
>>
> 
> 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
#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//

Reply via email to