Rework the driver to change the packet access technique based on the TAG
instead of the currently in use "id".
Since there is no way of knowing the OTP memory mapping in advance or the
changes it can go through with time, the id based approach is not reliable.
Accessing the packets based on the associated tags is a fail-proof
approach. This method is aided by adding a table of contents to store the
payload information which makes it easier to traverse through the OTP
memory and read the data of the intended packet. The stride of the nvmem
device is adjusted to 1 to support the TAG being treated as an offset.
The only reliable way to recognize a packet without being aware of the
flashed contents of the OTP memory is the TAG of the packet.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
drivers/nvmem/microchip-otpc.c | 130 +++++++++++++++++++++++++--------
1 file changed, 101 insertions(+), 29 deletions(-)
diff --git a/drivers/nvmem/microchip-otpc.c b/drivers/nvmem/microchip-otpc.c
index df979e8549fd..e922c882af72 100644
--- a/drivers/nvmem/microchip-otpc.c
+++ b/drivers/nvmem/microchip-otpc.c
@@ -18,16 +18,27 @@
#define MCHP_OTPC_CR_READ BIT(6)
#define MCHP_OTPC_MR (0x4)
#define MCHP_OTPC_MR_ADDR GENMASK(31, 16)
+#define MCHP_OTPC_MR_EMUL BIT(7)
#define MCHP_OTPC_AR (0x8)
#define MCHP_OTPC_SR (0xc)
#define MCHP_OTPC_SR_READ BIT(6)
#define MCHP_OTPC_HR (0x20)
#define MCHP_OTPC_HR_SIZE GENMASK(15, 8)
+#define MCHP_OTPC_HR_PACKET_TYPE GENMASK(2, 0)
#define MCHP_OTPC_DR (0x24)
#define MCHP_OTPC_NAME "mchp-otpc"
#define MCHP_OTPC_SIZE (11 * 1024)
+enum packet_type {
+ PACKET_TYPE_REGULAR = 1,
+ PACKET_TYPE_KEY = 2,
+ PACKET_TYPE_BOOT_CONFIG = 3,
+ PACKET_TYPE_SECURE_BOOT_CONFIG = 4,
+ PACKET_TYPE_HARDWARE_CONFIG = 5,
+ PACKET_TYPE_CUSTOM = 6,
+};
+
/**
* struct mchp_otpc - OTPC private data structure
* @base: base address
@@ -42,6 +53,25 @@ struct mchp_otpc {
u32 npackets;
};
+/**
+ * struct mchp_otpc_payload_info - OTP packet's payload information
+ * retrieved from the packet's header
+ * @id: driver assigned packet ID
+ * @packet_offset: offset address of the packet to be written in the
+ * register OTPC_MR.ADDR to access the packet
+ * @payload_length: length of the packet's payload
+ * @packet_type: type of the packet
+ * @packet_tag: TAG corresponding to the packet. Applicable for most
+ * of the regular packets
+ */
+struct mchp_otpc_payload_info {
+ u32 id;
+ u32 packet_offset;
+ u32 payload_length;
+ u32 packet_type;
+ u32 packet_tag;
+};
+
/**
* struct mchp_otpc_packet - OTPC packet data structure
* @list: list head
@@ -50,20 +80,16 @@ struct mchp_otpc {
*/
struct mchp_otpc_packet {
struct list_head list;
- u32 id;
- u32 offset;
+ struct mchp_otpc_payload_info payload_info;
};
-static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,
- u32 id)
+static struct mchp_otpc_packet *mchp_otpc_tag_to_packet(struct mchp_otpc *otpc,
+ u32 tag)
{
struct mchp_otpc_packet *packet;
- if (id >= otpc->npackets)
- return NULL;
-
list_for_each_entry(packet, &otpc->packets, list) {
- if (packet->id == id)
+ if (packet->payload_info.packet_tag == tag)
return packet;
}
@@ -140,8 +166,27 @@ static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,
* offset returned by hardware.
*
* For this, the read function will return the first requested bytes in the
- * packet. The user will have to be aware of the memory footprint before doing
- * the read request.
+ * packet. The user won't have to be aware of the memory footprint before doing
+ * the read request since it is abstracted and taken care by this driver.
+ *
+ * There is no way of knowing the Mapping of the OTP memory table in advance. In
+ * this read function the offset requested is treated as the identifier string
+ * i.e., Packet TAG, to acquire the payload with reliability. The packet Tag
+ * is the only way to recognize a packet without being aware of the flashed
+ * OTP memory map table.
+ */
+
+/**
+ * mchp_otpc_read() - Read the OTP packets and fill the buffer based on the TAG
+ * of the packet treated as the offset.
+ * @priv: Pointer to device structure.
+ * @off: offset of the OTP packet to be read. In this case, the TAG of the
+ * corresponding packet.
+ * @val: Pointer to data buffer
+ * @bytes: length of the buffer
+ *
+ * A value of zero will be returned on success, a negative errno will be
+ * returned in error cases.
*/
static int mchp_otpc_read(void *priv, unsigned int off, void *val,
size_t bytes)
@@ -154,30 +199,23 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val,
int ret, payload_size;
/*
- * We reach this point with off being multiple of stride = 4 to
- * be able to cross the subsystem. Inside the driver we use continuous
- * unsigned integer numbers for packet id, thus divide off by 4
- * before passing it to mchp_otpc_id_to_packet().
+ * From this point the packet tag received as the offset has to be translated
+ * into the actual packet. For this we traverse the table of contents stored
+ * in a list "packet" and look for the tag.
*/
- packet = mchp_otpc_id_to_packet(otpc, off / 4);
+
+ packet = mchp_otpc_tag_to_packet(otpc, off);
if (!packet)
return -EINVAL;
- offset = packet->offset;
+ offset = packet->payload_info.packet_offset;
- while (len < bytes) {
+ if (len < bytes) {
ret = mchp_otpc_prepare_read(otpc, offset);
if (ret)
return ret;
- /* Read and save header content. */
- *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR);
- len += sizeof(*buf);
- offset++;
- if (len >= bytes)
- break;
-
/* Read and save payload content. */
- payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1));
+ payload_size = packet->payload_info.payload_length;
writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);
do {
*buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR);
@@ -190,6 +228,20 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val,
return 0;
}
+static int mchp_otpc_read_packet_tag(struct mchp_otpc *otpc, unsigned int offset, unsigned int *val)
+{
+ int ret;
+
+ ret = mchp_otpc_prepare_read(otpc, offset);
+ if (ret)
+ return ret;
+
+ writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR);
+ *val = readl_relaxed(otpc->base + MCHP_OTPC_DR);
+
+ return 0;
+}
+
static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
{
struct mchp_otpc_packet *packet;
@@ -213,8 +265,15 @@ static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
if (!packet)
return -ENOMEM;
- packet->id = id++;
- packet->offset = word_pos;
+ packet->payload_info.id = id++;
+ packet->payload_info.packet_offset = word_pos;
+ packet->payload_info.payload_length = payload_size;
+ packet->payload_info.packet_type = FIELD_GET(MCHP_OTPC_HR_PACKET_TYPE, word);
+
+ if (packet->payload_info.packet_type == PACKET_TYPE_REGULAR)
+ ret = mchp_otpc_read_packet_tag(otpc, packet->payload_info.packet_offset,
+ &packet->payload_info.packet_tag);
+
INIT_LIST_HEAD(&packet->list);
list_add_tail(&packet->list, &otpc->packets);
@@ -236,7 +295,7 @@ static struct nvmem_config mchp_nvmem_config = {
.type = NVMEM_TYPE_OTP,
.read_only = true,
.word_size = 4,
- .stride = 4,
+ .stride = 1,
.reg_read = mchp_otpc_read,
};
@@ -244,8 +303,9 @@ static int mchp_otpc_probe(struct platform_device *pdev)
{
struct nvmem_device *nvmem;
struct mchp_otpc *otpc;
- u32 size;
+ u32 size, tmp;
int ret;
+ bool emul_enable;
otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);
if (!otpc)
@@ -256,10 +316,22 @@ static int mchp_otpc_probe(struct platform_device *pdev)
return PTR_ERR(otpc->base);
otpc->dev = &pdev->dev;
+
+ tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);
+ emul_enable = tmp & MCHP_OTPC_MR_EMUL;
+ if (emul_enable)
+ dev_info(otpc->dev, "Emulation mode enabled\n");
+
ret = mchp_otpc_init_packets_list(otpc, &size);
if (ret)
return ret;
+ if (size == 0) {
+ dev_err(otpc->dev, "Cannot access OTP memory !\n");
+ if (!emul_enable)
+ dev_err(otpc->dev, "Boot packet not configured & Emulation mode not enabled !\n");
+ }
+
mchp_nvmem_config.dev = otpc->dev;
mchp_nvmem_config.add_legacy_fixed_of_cells = true;
mchp_nvmem_config.size = size;
--
2.34.1
Hi, Varshini, On 8/4/25 13:02, Varshini Rajendran wrote: > Rework the driver to change the packet access technique based on the TAG > instead of the currently in use "id". > > Since there is no way of knowing the OTP memory mapping in advance or the > changes it can go through with time, the id based approach is not reliable. > Accessing the packets based on the associated tags is a fail-proof > approach. This method is aided by adding a table of contents to store the > payload information which makes it easier to traverse through the OTP > memory and read the data of the intended packet. The stride of the nvmem > device is adjusted to 1 to support the TAG being treated as an offset. > The only reliable way to recognize a packet without being aware of the > flashed contents of the OTP memory is the TAG of the packet. > > Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com> > --- > drivers/nvmem/microchip-otpc.c | 130 +++++++++++++++++++++++++-------- > 1 file changed, 101 insertions(+), 29 deletions(-) > > diff --git a/drivers/nvmem/microchip-otpc.c b/drivers/nvmem/microchip-otpc.c > index df979e8549fd..e922c882af72 100644 > --- a/drivers/nvmem/microchip-otpc.c > +++ b/drivers/nvmem/microchip-otpc.c > @@ -18,16 +18,27 @@ > #define MCHP_OTPC_CR_READ BIT(6) > #define MCHP_OTPC_MR (0x4) > #define MCHP_OTPC_MR_ADDR GENMASK(31, 16) > +#define MCHP_OTPC_MR_EMUL BIT(7) > #define MCHP_OTPC_AR (0x8) > #define MCHP_OTPC_SR (0xc) > #define MCHP_OTPC_SR_READ BIT(6) > #define MCHP_OTPC_HR (0x20) > #define MCHP_OTPC_HR_SIZE GENMASK(15, 8) > +#define MCHP_OTPC_HR_PACKET_TYPE GENMASK(2, 0) > #define MCHP_OTPC_DR (0x24) > > #define MCHP_OTPC_NAME "mchp-otpc" > #define MCHP_OTPC_SIZE (11 * 1024) > > +enum packet_type { > + PACKET_TYPE_REGULAR = 1, > + PACKET_TYPE_KEY = 2, > + PACKET_TYPE_BOOT_CONFIG = 3, > + PACKET_TYPE_SECURE_BOOT_CONFIG = 4, > + PACKET_TYPE_HARDWARE_CONFIG = 5, > + PACKET_TYPE_CUSTOM = 6, You can drop unused packet types. > +}; > + > /** > * struct mchp_otpc - OTPC private data structure > * @base: base address > @@ -42,6 +53,25 @@ struct mchp_otpc { > u32 npackets; > }; > > +/** > + * struct mchp_otpc_payload_info - OTP packet's payload information > + * retrieved from the packet's header > + * @id: driver assigned packet ID > + * @packet_offset: offset address of the packet to be written in the > + * register OTPC_MR.ADDR to access the packet > + * @payload_length: length of the packet's payload > + * @packet_type: type of the packet > + * @packet_tag: TAG corresponding to the packet. Applicable for most > + * of the regular packets > + */ > +struct mchp_otpc_payload_info { > + u32 id; > + u32 packet_offset; Can you keep packet specific members in struct mchp_otpc_packet? > + u32 payload_length; > + u32 packet_type; > + u32 packet_tag; Will this technique limit the number of packets that can be in memory? I think this is not an issue with the current devices? What about the upcomming? > +}; > + > /** > * struct mchp_otpc_packet - OTPC packet data structure > * @list: list head > @@ -50,20 +80,16 @@ struct mchp_otpc { > */ > struct mchp_otpc_packet { > struct list_head list; > - u32 id; > - u32 offset; > + struct mchp_otpc_payload_info payload_info; Warning: ../drivers/nvmem/microchip-otpc.c:83 struct member 'payload_info' not described in 'mchp_otpc_packet' > }; > > -static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc, > - u32 id) > +static struct mchp_otpc_packet *mchp_otpc_tag_to_packet(struct mchp_otpc *otpc, > + u32 tag) > { > struct mchp_otpc_packet *packet; > > - if (id >= otpc->npackets) > - return NULL; > - > list_for_each_entry(packet, &otpc->packets, list) { > - if (packet->id == id) > + if (packet->payload_info.packet_tag == tag) > return packet; > } > > @@ -140,8 +166,27 @@ static int mchp_otpc_prepare_read(struct mchp_otpc *otpc, > * offset returned by hardware. > * > * For this, the read function will return the first requested bytes in the > - * packet. The user will have to be aware of the memory footprint before doing > - * the read request. > + * packet. The user won't have to be aware of the memory footprint before doing > + * the read request since it is abstracted and taken care by this driver. > + * > + * There is no way of knowing the Mapping of the OTP memory table in advance. In > + * this read function the offset requested is treated as the identifier string > + * i.e., Packet TAG, to acquire the payload with reliability. The packet Tag > + * is the only way to recognize a packet without being aware of the flashed > + * OTP memory map table. > + */ > + > +/** > + * mchp_otpc_read() - Read the OTP packets and fill the buffer based on the TAG > + * of the packet treated as the offset. > + * @priv: Pointer to device structure. > + * @off: offset of the OTP packet to be read. In this case, the TAG of the > + * corresponding packet. > + * @val: Pointer to data buffer > + * @bytes: length of the buffer > + * > + * A value of zero will be returned on success, a negative errno will be > + * returned in error cases. > */ > static int mchp_otpc_read(void *priv, unsigned int off, void *val, > size_t bytes) > @@ -154,30 +199,23 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val, > int ret, payload_size; > > /* > - * We reach this point with off being multiple of stride = 4 to > - * be able to cross the subsystem. Inside the driver we use continuous > - * unsigned integer numbers for packet id, thus divide off by 4 > - * before passing it to mchp_otpc_id_to_packet(). > + * From this point the packet tag received as the offset has to be translated > + * into the actual packet. For this we traverse the table of contents stored > + * in a list "packet" and look for the tag. > */ > - packet = mchp_otpc_id_to_packet(otpc, off / 4); > + > + packet = mchp_otpc_tag_to_packet(otpc, off); > if (!packet) > return -EINVAL; If one would want to read the full content of the memory will this function return first time offset will not corresond to a valid packet? > - offset = packet->offset; > + offset = packet->payload_info.packet_offset; > > - while (len < bytes) { > + if (len < bytes) { The approach now is that a single packet can be read at a moment, right? Is this intended? > ret = mchp_otpc_prepare_read(otpc, offset); > if (ret) > return ret; > > - /* Read and save header content. */ > - *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR); > - len += sizeof(*buf); > - offset++; > - if (len >= bytes) > - break; > - Dropping this will not return the header content anymore. Is this intended? > /* Read and save payload content. */ > - payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1)); > + payload_size = packet->payload_info.payload_length; > writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR); > do { > *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR); > @@ -190,6 +228,20 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val, > return 0; > } > > +static int mchp_otpc_read_packet_tag(struct mchp_otpc *otpc, unsigned int offset, unsigned int *val) This exceeed 100 chars. > +{ > + int ret; > + > + ret = mchp_otpc_prepare_read(otpc, offset); > + if (ret) > + return ret; > + > + writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR); > + *val = readl_relaxed(otpc->base + MCHP_OTPC_DR); > + > + return 0; > +} > + > static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size) > { > struct mchp_otpc_packet *packet; > @@ -213,8 +265,15 @@ static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size) > if (!packet) > return -ENOMEM; > > - packet->id = id++; > - packet->offset = word_pos; > + packet->payload_info.id = id++; > + packet->payload_info.packet_offset = word_pos; > + packet->payload_info.payload_length = payload_size; > + packet->payload_info.packet_type = FIELD_GET(MCHP_OTPC_HR_PACKET_TYPE, word); > + > + if (packet->payload_info.packet_type == PACKET_TYPE_REGULAR) > + ret = mchp_otpc_read_packet_tag(otpc, packet->payload_info.packet_offset, > + &packet->payload_info.packet_tag); > + > INIT_LIST_HEAD(&packet->list); > list_add_tail(&packet->list, &otpc->packets); > > @@ -236,7 +295,7 @@ static struct nvmem_config mchp_nvmem_config = { > .type = NVMEM_TYPE_OTP, > .read_only = true, > .word_size = 4, > - .stride = 4, > + .stride = 1, > .reg_read = mchp_otpc_read, > }; > > @@ -244,8 +303,9 @@ static int mchp_otpc_probe(struct platform_device *pdev) > { > struct nvmem_device *nvmem; > struct mchp_otpc *otpc; > - u32 size; > + u32 size, tmp; > int ret; > + bool emul_enable; > > otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL); > if (!otpc) > @@ -256,10 +316,22 @@ static int mchp_otpc_probe(struct platform_device *pdev) > return PTR_ERR(otpc->base); > > otpc->dev = &pdev->dev; > + > + tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR); > + emul_enable = tmp & MCHP_OTPC_MR_EMUL; > + if (emul_enable) > + dev_info(otpc->dev, "Emulation mode enabled\n"); Can you update the commit with the meaning of these? > + > ret = mchp_otpc_init_packets_list(otpc, &size); > if (ret) > return ret; > > + if (size == 0) { > + dev_err(otpc->dev, "Cannot access OTP memory !\n"); Space before ! > + if (!emul_enable) > + dev_err(otpc->dev, "Boot packet not configured & Emulation mode not enabled !\n"); Do you still want to register the driver in case size == 0? Thank you, Claudiu > + } > + > mchp_nvmem_config.dev = otpc->dev; > mchp_nvmem_config.add_legacy_fixed_of_cells = true; > mchp_nvmem_config.size = size;
On Mon, Aug 4, 2025 at 12:03 PM Varshini Rajendran <varshini.rajendran@microchip.com> wrote: > > Rework the driver to change the packet access technique based on the TAG > instead of the currently in use "id". > > Since there is no way of knowing the OTP memory mapping in advance or the > changes it can go through with time, the id based approach is not reliable. > Accessing the packets based on the associated tags is a fail-proof > approach. This method is aided by adding a table of contents to store the > payload information which makes it easier to traverse through the OTP > memory and read the data of the intended packet. The stride of the nvmem > device is adjusted to 1 to support the TAG being treated as an offset. > The only reliable way to recognize a packet without being aware of the > flashed contents of the OTP memory is the TAG of the packet. ... > +struct mchp_otpc_payload_info { > + u32 id; > + u32 packet_offset; > + u32 payload_length; > + u32 packet_type; > + u32 packet_tag; Just wondering if these are okay to be in CPU endianess, i.o.w. is this part of the protocol to HW, or is it simply Linux driver holding values? > +}; ... > +/** > + * mchp_otpc_read() - Read the OTP packets and fill the buffer based on the TAG > + * of the packet treated as the offset. > + * @priv: Pointer to device structure. > + * @off: offset of the OTP packet to be read. In this case, the TAG of the > + * corresponding packet. > + * @val: Pointer to data buffer > + * @bytes: length of the buffer > + * > + * A value of zero will be returned on success, a negative errno will be > + * returned in error cases. kernel-doc validator is not happy: Missing Return section. > */ ... > - packet = mchp_otpc_id_to_packet(otpc, off / 4); > + Seems like a stray change (adding a blank line). > + packet = mchp_otpc_tag_to_packet(otpc, off); > if (!packet) > return -EINVAL; ... > + writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR); Does UL give any value / make any sense here? ... > + if (size == 0) { > + dev_err(otpc->dev, "Cannot access OTP memory !\n"); > + if (!emul_enable) > + dev_err(otpc->dev, "Boot packet not configured & Emulation mode not enabled !\n"); Stray space before the exclamation mark in both messages. > + } -- With Best Regards, Andy Shevchenko
Hi Varshini, kernel test robot noticed the following build warnings: [auto build test WARNING on robh/for-next] [also build test WARNING on jic23-iio/togreg v6.16] [cannot apply to linus/master next-20250804] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Varshini-Rajendran/ARM-dts-microchip-sama7d65-add-cpu-opps/20250804-180839 base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next patch link: https://lore.kernel.org/r/20250804100219.63325-3-varshini.rajendran%40microchip.com patch subject: [PATCH 02/15] nvmem: microchip-otpc: rework to access packets based on tag config: arc-randconfig-002-20250804 (https://download.01.org/0day-ci/archive/20250805/202508050141.HKFcYdLy-lkp@intel.com/config) compiler: arc-linux-gcc (GCC) 13.4.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250805/202508050141.HKFcYdLy-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202508050141.HKFcYdLy-lkp@intel.com/ All warnings (new ones prefixed by >>): >> Warning: drivers/nvmem/microchip-otpc.c:83 struct member 'payload_info' not described in 'mchp_otpc_packet' >> Warning: drivers/nvmem/microchip-otpc.c:83 Excess struct member 'id' description in 'mchp_otpc_packet' >> Warning: drivers/nvmem/microchip-otpc.c:83 Excess struct member 'offset' description in 'mchp_otpc_packet' -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
On Mon, Aug 04, 2025 at 03:32:06PM +0530, Varshini Rajendran wrote: > +static struct mchp_otpc_packet *mchp_otpc_tag_to_packet(struct mchp_otpc *otpc, > + u32 tag) > { > struct mchp_otpc_packet *packet; > > - if (id >= otpc->npackets) > - return NULL; > - > list_for_each_entry(packet, &otpc->packets, list) { > - if (packet->id == id) > + if (packet->payload_info.packet_tag == tag) > return packet; > } As mentioned in bindings change, this looks like breaking all the users. Best regards, Krzysztof
© 2016 - 2025 Red Hat, Inc.