diff mbox series

[net-next,2/2] net: dsa: realtek: load switch variants on demand

Message ID 20231117235140.1178-3-luizluca@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series net: dsa: realtek: Introduce realtek_common, load variants on demand | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline fail Detected static functions without inline keyword in header files: 1
netdev/build_32bit success Errors and warnings before: 1127 this patch: 1127
netdev/cc_maintainers success CCed 9 of 9 maintainers
netdev/build_clang success Errors and warnings before: 1154 this patch: 1154
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1154 this patch: 1154
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 328 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Luiz Angelo Daros de Luca Nov. 17, 2023, 11:50 p.m. UTC
realtek-common had a hard dependency on both switch variants. As a
result, it was not possible to selectively load only one model at
runtime. Now, variants are registered in the realtek-common module, and
interface modules look for a variant using the compatible string.

The variant modules use the same compatible string as the module alias.
This way, an interface module can use the matching compatible string to
both load the module and get the variant reference.

Signed-off-by: Luiz Angelo Daros de Luca <luizluca@gmail.com>
---
 drivers/net/dsa/realtek/realtek-common.c | 107 ++++++++++++++++++++---
 drivers/net/dsa/realtek/realtek-common.h |  33 +++++++
 drivers/net/dsa/realtek/realtek-mdio.c   |   9 +-
 drivers/net/dsa/realtek/realtek-smi.c    |  21 +++--
 drivers/net/dsa/realtek/realtek.h        |   3 -
 drivers/net/dsa/realtek/rtl8365mb.c      |   4 +-
 drivers/net/dsa/realtek/rtl8366rb.c      |   4 +-
 7 files changed, 154 insertions(+), 27 deletions(-)

Comments

Vladimir Oltean Nov. 19, 2023, 12:19 p.m. UTC | #1
On Fri, Nov 17, 2023 at 08:50:01PM -0300, Luiz Angelo Daros de Luca wrote:
> diff --git a/drivers/net/dsa/realtek/realtek-mdio.c b/drivers/net/dsa/realtek/realtek-mdio.c
> index b865e11955ca..c447dd815a59 100644
> --- a/drivers/net/dsa/realtek/realtek-mdio.c
> +++ b/drivers/net/dsa/realtek/realtek-mdio.c
> @@ -145,7 +145,7 @@ static int realtek_mdio_probe(struct mdio_device *mdiodev)
>  	ret = priv->ops->detect(priv);
>  	if (ret) {
>  		dev_err(dev, "unable to detect switch\n");
> -		return ret;
> +		goto err_variant_put;
>  	}
>  
>  	priv->ds->ops = priv->variant->ds_ops_mdio;
> @@ -154,10 +154,15 @@ static int realtek_mdio_probe(struct mdio_device *mdiodev)
>  	ret = dsa_register_switch(priv->ds);
>  	if (ret) {
>  		dev_err_probe(dev, ret, "unable to register switch\n");
> -		return ret;
> +		goto err_variant_put;
>  	}
>  
>  	return 0;
> +
> +err_variant_put:
> +	realtek_variant_put(priv->variant);
> +
> +	return ret;
>  }
>  
>  static void realtek_mdio_remove(struct mdio_device *mdiodev)

This is not so great at all from an API presentation point of view - the
fact that the caller needs to know that realtek_variant_put() undoes
the effect of realtek_common_probe().

You said you don't like too many abstractions, and fair enough, but
maybe we could have
- realtek_common_probe_pre(), realtek_common_remove_pre()
- realtek_common_probe_post(), realtek_common_remove_post()

which leads to even more code sharing from probe(), as well as an
opportunity to have a clearly matched unwind function for everything
that is done in probe()?
Krzysztof Kozlowski Nov. 20, 2023, 9:20 a.m. UTC | #2
On 18/11/2023 00:50, Luiz Angelo Daros de Luca wrote:
> realtek-common had a hard dependency on both switch variants. As a
> result, it was not possible to selectively load only one model at
> runtime. Now, variants are registered in the realtek-common module, and
> interface modules look for a variant using the compatible string.

...

> diff --git a/drivers/net/dsa/realtek/realtek-common.h b/drivers/net/dsa/realtek/realtek-common.h
> index 90a949386518..6de4991d8b5c 100644
> --- a/drivers/net/dsa/realtek/realtek-common.h
> +++ b/drivers/net/dsa/realtek/realtek-common.h
> @@ -5,6 +5,37 @@
>  
>  #include <linux/regmap.h>
>  
> +struct realtek_variant_entry {
> +	const struct realtek_variant *variant;
> +	const char *compatible;
> +	struct module *owner;
> +	struct list_head list;
> +};
> +
> +#define module_realtek_variant(__variant, __compatible)			\
> +static struct realtek_variant_entry __variant ## _entry = {		\
> +	.compatible = __compatible,					\
> +	.variant = &(__variant),					\
> +	.owner = THIS_MODULE,						\
> +};									\
> +static int __init realtek_variant_module_init(void)			\
> +{									\
> +	realtek_variant_register(&__variant ## _entry);			\
> +	return 0;							\
> +}									\
> +module_init(realtek_variant_module_init)				\
> +									\
> +static void __exit realtek_variant_module_exit(void)			\
> +{									\
> +	realtek_variant_unregister(&__variant ## _entry);		\
> +}									\
> +module_exit(realtek_variant_module_exit);				\
> +									\
> +MODULE_ALIAS(__compatible)

No, why do you need it? You should not need MODULE_ALIAS() in normal
cases. If you need it, usually it means your device ID table is wrong
(e.g. misses either entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is
not a substitute for incomplete ID table.

Entire abstraction/macro is pointless and make the code less readable.


Best regards,
Krzysztof
Vladimir Oltean Nov. 20, 2023, 1:48 p.m. UTC | #3
Hi Krzysztof,

On Mon, Nov 20, 2023 at 10:20:13AM +0100, Krzysztof Kozlowski wrote:
> No, why do you need it? You should not need MODULE_ALIAS() in normal
> cases. If you need it, usually it means your device ID table is wrong
> (e.g. misses either entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is
> not a substitute for incomplete ID table.
> 
> Entire abstraction/macro is pointless and make the code less readable.

Are you saying that the line

MODULE_DEVICE_TABLE(of, realtek_common_of_match);

should be put in all of realtek-mdio.c, realtek-smi.c, rtl8365mb.c and
rtl8366rb.c, but not in realtek-common.c?

There are 5 kernel modules involved, 2 for interfaces and 2 for switches.

Even if the same OF device ID table could be used to load multiple
modules, I'm not sure
(a) how to avoid loading the interface driver which will not be used
    (SMI if it's a MDIO-connected switch, or MDIO if it's an SMI
    connected switch)
(b) how to ensure that the drivers are loaded in the right order, i.e.
    the switch drivers are loaded before the interface drivers
Krzysztof Kozlowski Nov. 20, 2023, 2:07 p.m. UTC | #4
On 20/11/2023 14:48, Vladimir Oltean wrote:
> Hi Krzysztof,
> 
> On Mon, Nov 20, 2023 at 10:20:13AM +0100, Krzysztof Kozlowski wrote:
>> No, why do you need it? You should not need MODULE_ALIAS() in normal
>> cases. If you need it, usually it means your device ID table is wrong
>> (e.g. misses either entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is
>> not a substitute for incomplete ID table.
>>
>> Entire abstraction/macro is pointless and make the code less readable.
> 
> Are you saying that the line
> 
> MODULE_DEVICE_TABLE(of, realtek_common_of_match);
> 
> should be put in all of realtek-mdio.c, realtek-smi.c, rtl8365mb.c and
> rtl8366rb.c, but not in realtek-common.c?

Driver should use MODULE_DEVICE_TABLE() for the table not MODULE_ALIAS()
for each entry. I don't judge where should it be put. I just dislike
usage of aliases as a incomplete-substitute of proper table.

> 
> There are 5 kernel modules involved, 2 for interfaces and 2 for switches.
> 
> Even if the same OF device ID table could be used to load multiple
> modules, I'm not sure
> (a) how to avoid loading the interface driver which will not be used
>     (SMI if it's a MDIO-connected switch, or MDIO if it's an SMI
>     connected switch)
> (b) how to ensure that the drivers are loaded in the right order, i.e.
>     the switch drivers are loaded before the interface drivers

I am sorry, I do not understand the problem. The MODULE_DEVICE_TABLE and
MODULE_ALIAS create exactly the same behavior. How any of above would
happen with table but not with alias having exactly the same compatibles?

Best regards,
Krzysztof
Luiz Angelo Daros de Luca Nov. 21, 2023, 2:40 p.m. UTC | #5
Hi Krzysztof,

> > On Mon, Nov 20, 2023 at 10:20:13AM +0100, Krzysztof Kozlowski wrote:
> >> No, why do you need it? You should not need MODULE_ALIAS() in normal
> >> cases. If you need it, usually it means your device ID table is wrong
> >> (e.g. misses either entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is
> >> not a substitute for incomplete ID table.
> >>
> >> Entire abstraction/macro is pointless and make the code less readable.
> >
> > Are you saying that the line
> >
> > MODULE_DEVICE_TABLE(of, realtek_common_of_match);
> >
> > should be put in all of realtek-mdio.c, realtek-smi.c, rtl8365mb.c and
> > rtl8366rb.c, but not in realtek-common.c?
>
> Driver should use MODULE_DEVICE_TABLE() for the table not MODULE_ALIAS()
> for each entry. I don't judge where should it be put. I just dislike
> usage of aliases as a incomplete-substitute of proper table.

MODULE_ALIAS() is in use here because of its relation with modprobe,
not inside the kernel.
MODULE_DEVICE_TABLE is also in use but it does not seem to generate
any information usable by modprobe.

> >
> > There are 5 kernel modules involved, 2 for interfaces and 2 for switches.
> >
> > Even if the same OF device ID table could be used to load multiple
> > modules, I'm not sure
> > (a) how to avoid loading the interface driver which will not be used
> >     (SMI if it's a MDIO-connected switch, or MDIO if it's an SMI
> >     connected switch)
> > (b) how to ensure that the drivers are loaded in the right order, i.e.
> >     the switch drivers are loaded before the interface drivers
>
> I am sorry, I do not understand the problem. The MODULE_DEVICE_TABLE and
> MODULE_ALIAS create exactly the same behavior. How any of above would
> happen with table but not with alias having exactly the same compatibles?

Realtek switches can be managed through different interfaces. In the
current kernel implementation, there is an MDIO driver (realtek-mdio)
for switches connected to the MDIO bus, and a platform driver
implementing the SMI protocol (a simple GPIO bit-bang).

The actual switch logic is implemented in two different switch
family/variant modules: rtl8365mb and rtl8366 (currently only for
rtl8366rb). As of today, both Realtek interface modules directly
reference each switch variant symbol, creating a hard dependency and
forcing the interface module to load both switch family variants. Each
interface module provides the same compatible strings for both
variants, but I haven't investigated whether this is problematic or
not. It appears that there is no mechanism to autoload modules based
on compatible strings from the device tree, and each interface module
is a different type of driver.

This patch set accomplishes two things: it moves some shared code to a
new Realtek common module and changes the hard dependency between
interface and variant modules into a more dynamic relation. Each
variant module registers itself in realtek-common, and interface
modules look for the appropriate variant. However, as interface
modules do not directly depend on variant modules, they might not have
been loaded yet, causing the driver probe to fail.

The solution I opted for was to request the module during the
interface probe (similar to what happens with DSA tag modules),
triggering a userland "modprobe XXXX." Even without the variant module
loaded, we know the compatible string that matched the interface
driver. We can also have some extra info from match data, but I chose
to simply keep using the compatible string. The issue is how to get
the "XXXX" for modprobe. For DSA tags, module names are generated
according to a fixed rule based on the tag name. However, the switch
variants do not have a fixed relation between module name and switch
families (actually, there is not even a switch family name). I could
(and did in a previous RFC) use the match data to inform each module
name. However, it adds a non-obvious relation between the module name
(defined in Makefile) and a string in code. I was starting to
implement a lookup table to match compatible strings to their module
names when I realized that I was just mapping a string to a module
name, something like what module alias already does. That's when the
MODULE_ALIAS("<the compatible string>") was introduced.

After this discussion, I have some questions:

I'm declaring the of_device_id match table in realtek-common as it is
the same for both interfaces. I also moved MODULE_DEVICE_TABLE(of,
realtek_common_of_match) to realtek-common. Should I keep the
MODULE_DEVICE_TABLE() on each interface module (referencing the same
table), or is it okay to keep it in the realtek-common module? If I
need to move it to each interface module, can I reuse a shared
of_device_id match table declared in realtek-common?

If MODULE_ALIAS is not an acceptable solution, what would be the right
one? Go back to the static mapping between the compatible string and
the module name or is there a better solution?

Regards,

Luiz
Krzysztof Kozlowski Nov. 21, 2023, 10:15 p.m. UTC | #6
On 21/11/2023 15:40, Luiz Angelo Daros de Luca wrote:
> Hi Krzysztof,
> 
>>> On Mon, Nov 20, 2023 at 10:20:13AM +0100, Krzysztof Kozlowski wrote:
>>>> No, why do you need it? You should not need MODULE_ALIAS() in normal
>>>> cases. If you need it, usually it means your device ID table is wrong
>>>> (e.g. misses either entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is
>>>> not a substitute for incomplete ID table.
>>>>
>>>> Entire abstraction/macro is pointless and make the code less readable.
>>>
>>> Are you saying that the line
>>>
>>> MODULE_DEVICE_TABLE(of, realtek_common_of_match);
>>>
>>> should be put in all of realtek-mdio.c, realtek-smi.c, rtl8365mb.c and
>>> rtl8366rb.c, but not in realtek-common.c?
>>
>> Driver should use MODULE_DEVICE_TABLE() for the table not MODULE_ALIAS()
>> for each entry. I don't judge where should it be put. I just dislike
>> usage of aliases as a incomplete-substitute of proper table.
> 
> MODULE_ALIAS() is in use here because of its relation with modprobe,
> not inside the kernel.

The same as MODULE_DEVICE_TABLE.

> MODULE_DEVICE_TABLE is also in use but it does not seem to generate
> any information usable by modprobe.

It does, exactly the same from functional point of view... which
information are you missing?

> 
>>>
>>> There are 5 kernel modules involved, 2 for interfaces and 2 for switches.
>>>
>>> Even if the same OF device ID table could be used to load multiple
>>> modules, I'm not sure
>>> (a) how to avoid loading the interface driver which will not be used
>>>     (SMI if it's a MDIO-connected switch, or MDIO if it's an SMI
>>>     connected switch)
>>> (b) how to ensure that the drivers are loaded in the right order, i.e.
>>>     the switch drivers are loaded before the interface drivers
>>
>> I am sorry, I do not understand the problem. The MODULE_DEVICE_TABLE and
>> MODULE_ALIAS create exactly the same behavior. How any of above would
>> happen with table but not with alias having exactly the same compatibles?
> 
> Realtek switches can be managed through different interfaces. In the
> current kernel implementation, there is an MDIO driver (realtek-mdio)
> for switches connected to the MDIO bus, and a platform driver
> implementing the SMI protocol (a simple GPIO bit-bang).
> 
> The actual switch logic is implemented in two different switch
> family/variant modules: rtl8365mb and rtl8366 (currently only for
> rtl8366rb). As of today, both Realtek interface modules directly
> reference each switch variant symbol, creating a hard dependency and
> forcing the interface module to load both switch family variants. Each
> interface module provides the same compatible strings for both
> variants, but I haven't investigated whether this is problematic or
> not. It appears that there is no mechanism to autoload modules based
> on compatible strings from the device tree, and each interface module
> is a different type of driver.

??? So entire Linux kernel is broken? Somehow autoloading modules based
on compatible strings from the device tree works for every other driver
properly using tables... So again, I am repeating:
stop using MODULE_ALIAS for missing/incomplete tables

> 
> This patch set accomplishes two things: it moves some shared code to a

Which should not... One thing per patch.


> new Realtek common module and changes the hard dependency between
> interface and variant modules into a more dynamic relation. Each
> variant module registers itself in realtek-common, and interface
> modules look for the appropriate variant. However, as interface
> modules do not directly depend on variant modules, they might not have
> been loaded yet, causing the driver probe to fail.
> 
> The solution I opted for was to request the module during the
> interface probe (similar to what happens with DSA tag modules),
> triggering a userland "modprobe XXXX." Even without the variant module
> loaded, we know the compatible string that matched the interface
> driver. We can also have some extra info from match data, but I chose
> to simply keep using the compatible string. The issue is how to get

How is this even related to the problem? Please respond with concise
messages.

> the "XXXX" for modprobe. For DSA tags, module names are generated
> according to a fixed rule based on the tag name. However, the switch
> variants do not have a fixed relation between module name and switch
> families (actually, there is not even a switch family name). I could
> (and did in a previous RFC) use the match data to inform each module
> name. However, it adds a non-obvious relation between the module name
> (defined in Makefile) and a string in code. I was starting to
> implement a lookup table to match compatible strings to their module
> names when I realized that I was just mapping a string to a module
> name, something like what module alias already does. That's when the
> MODULE_ALIAS("<the compatible string>") was introduced.
> 
> After this discussion, I have some questions:
> 
> I'm declaring the of_device_id match table in realtek-common as it is
> the same for both interfaces. I also moved MODULE_DEVICE_TABLE(of,
> realtek_common_of_match) to realtek-common. Should I keep the
> MODULE_DEVICE_TABLE() on each interface module (referencing the same
> table), or is it okay to keep it in the realtek-common module? If I

Why would you have the same compatible entries in different modules? You
do understand that device node will become populated on first bind (bind
of the first device)?

> need to move it to each interface module, can I reuse a shared
> of_device_id match table declared in realtek-common?
> 
> If MODULE_ALIAS is not an acceptable solution, what would be the right
> one? Go back to the static mapping between the compatible string and
> the module name or is there a better solution?


Best regards,
Krzysztof
Krzysztof Kozlowski Nov. 21, 2023, 10:35 p.m. UTC | #7
On 21/11/2023 23:15, Krzysztof Kozlowski wrote:
> How is this even related to the problem? Please respond with concise
> messages.
> 
>> the "XXXX" for modprobe. For DSA tags, module names are generated
>> according to a fixed rule based on the tag name. However, the switch
>> variants do not have a fixed relation between module name and switch
>> families (actually, there is not even a switch family name). I could
>> (and did in a previous RFC) use the match data to inform each module
>> name. However, it adds a non-obvious relation between the module name
>> (defined in Makefile) and a string in code. I was starting to
>> implement a lookup table to match compatible strings to their module
>> names when I realized that I was just mapping a string to a module
>> name, something like what module alias already does. That's when the
>> MODULE_ALIAS("<the compatible string>") was introduced.
>>
>> After this discussion, I have some questions:
>>
>> I'm declaring the of_device_id match table in realtek-common as it is
>> the same for both interfaces. I also moved MODULE_DEVICE_TABLE(of,
>> realtek_common_of_match) to realtek-common. Should I keep the
>> MODULE_DEVICE_TABLE() on each interface module (referencing the same
>> table), or is it okay to keep it in the realtek-common module? If I
> 
> Why would you have the same compatible entries in different modules? You
> do understand that device node will become populated on first bind (bind
> of the first device)?
> 
>> need to move it to each interface module, can I reuse a shared
>> of_device_id match table declared in realtek-common?
>>
>> If MODULE_ALIAS is not an acceptable solution, what would be the right
>> one? Go back to the static mapping between the compatible string and
>> the module name or is there a better solution?

Probably the solution is to make the design the same as for all other
complex drivers supporting more than one bus. If your ID table is
defined in modules A and B, then their loading should not depend on
aliases put in some additional "common" module. We solved this many
times for devices residing on multiple buses (e.g. I2C and SPI), so why
this has to be done in reverse order?

If you ask what would be the acceptable solution, then my answer is: do
the same as for most of other drivers, do not reinvent stuff like
putting same ID table or module alias in two modules. The table is
defined only once in each driver being loaded on uevent. From that
driver you probe whatever device you have, including calling any common
code, subprobes, subvariants etc.

Best regards,
Krzysztof
Luiz Angelo Daros de Luca Nov. 22, 2023, 10:44 p.m. UTC | #8
> On 21/11/2023 23:15, Krzysztof Kozlowski wrote:
> > How is this even related to the problem? Please respond with concise
> > messages.
> >
> >> the "XXXX" for modprobe. For DSA tags, module names are generated
> >> according to a fixed rule based on the tag name. However, the switch
> >> variants do not have a fixed relation between module name and switch
> >> families (actually, there is not even a switch family name). I could
> >> (and did in a previous RFC) use the match data to inform each module
> >> name. However, it adds a non-obvious relation between the module name
> >> (defined in Makefile) and a string in code. I was starting to
> >> implement a lookup table to match compatible strings to their module
> >> names when I realized that I was just mapping a string to a module
> >> name, something like what module alias already does. That's when the
> >> MODULE_ALIAS("<the compatible string>") was introduced.
> >>
> >> After this discussion, I have some questions:
> >>
> >> I'm declaring the of_device_id match table in realtek-common as it is
> >> the same for both interfaces. I also moved MODULE_DEVICE_TABLE(of,
> >> realtek_common_of_match) to realtek-common. Should I keep the
> >> MODULE_DEVICE_TABLE() on each interface module (referencing the same
> >> table), or is it okay to keep it in the realtek-common module? If I
> >
> > Why would you have the same compatible entries in different modules? You
> > do understand that device node will become populated on first bind (bind
> > of the first device)?
> >
> >> need to move it to each interface module, can I reuse a shared
> >> of_device_id match table declared in realtek-common?
> >>
> >> If MODULE_ALIAS is not an acceptable solution, what would be the right
> >> one? Go back to the static mapping between the compatible string and
> >> the module name or is there a better solution?
>
> Probably the solution is to make the design the same as for all other
> complex drivers supporting more than one bus. If your ID table is
> defined in modules A and B, then their loading should not depend on
> aliases put in some additional "common" module. We solved this many
> times for devices residing on multiple buses (e.g. I2C and SPI), so why
> this has to be done in reverse order?
>
> If you ask what would be the acceptable solution, then my answer is: do
> the same as for most of other drivers, do not reinvent stuff like
> putting same ID table or module alias in two modules. The table is
> defined only once in each driver being loaded on uevent. From that
> driver you probe whatever device you have, including calling any common
> code, subprobes, subvariants etc.

Thank you, Krzysztof. I didn't design the driver in question; I was
just trying to cooperate.

Linus, Alvin, these are originally your work. I might have
misunderstood something and would appreciate some help in this
discussion.

I noticed that drivers like bmp280, inv_mpu6050, and adxl345 share a
common core with two interface drivers, both declaring the same
compatible strings. Is it an issue to have the same compatible string
in different modules? Is there a better reference to follow?

In realtek DSA drivers, the "interface" modules (realtek-smi and
realtek-mdio) are the actual drivers, containing compatible strings.
The subdriver/variant/family modules are just the switch logic with no
driver declaration. Currently, all subdrivers are loaded, which might
not be necessary as a device may not use two different realtek switch
families. Ideally, only one should be loaded. Can we request a module
at runtime, and is it acceptable to use MODULE_ALIAS() for a
"non-driver module" to make that easier?

The driver works without the module alias/request if it was loaded
before the interface driver. I aim to make it work in more scenarios.
These drivers are for embedded systems, so handling uevents might not
be feasible. I used the compatible string to avoid creating a new
string for the family name, but I could use anything else like
"rtl8366rb" only when the module name does not match the model used by
the compatible string. I didn't expect it to be used by anything
monitoring uevents.

For the driver structure, we have a few options:

1) Merge everything into a single realtek-switch, declaring the
compatible strings once and implementing it as both a platform and an
mdio driver.
2) Have a module for each interface (realtek-smi and realtek-mdio),
both repeating the compatible strings for all families, and load the
family as an interface dependency (the current approach).
3) Introduce a realtek-common module, a module for each interface,
both repeating the compatible strings for all families, and load the
family as an interface dependency (the first patch in this series).
4) Introduce a realtek-common module, a module for each interface,
both repeating the compatible strings for all families, and expect the
family module to be already loaded.
5) Introduce a realtek-common module, a module for each interface,
both repeating the compatible strings for all families, and request
the family module to be loaded (the last patch in this series).
6) Introduce a realtek-common module, merging everything from
realtek-smi and realtek-mdio but the driver declarations and implement
each family as a single module that works both as a platform and an
mdio driver.
7) Introduce a realtek-common module, merging everything from
realtek-smi and realtek-mdio but the driver declarations and create
two modules for each family as real drivers, repeating the compatible
strings on each family (e.g., {rtl8366rb, rtl8365mb}_{smi,mdio}.ko).

Solutions 1, 2, and 3 may use more resources than needed. My test
device, for example, cannot handle them. Solution 7 is similar to the
examples I found in the kernel. Solutions 1 and 6 are the only ones
not repeating the same compatible strings in different modules, if
that's a problem.

Regards,

Luiz
Alvin Šipraga Nov. 23, 2023, 2:05 a.m. UTC | #9
On Wed, Nov 22, 2023 at 07:44:44PM -0300, Luiz Angelo Daros de Luca wrote:
> For the driver structure, we have a few options:
> 
> 1) Merge everything into a single realtek-switch, declaring the
> compatible strings once and implementing it as both a platform and an
> mdio driver.
> 2) Have a module for each interface (realtek-smi and realtek-mdio),
> both repeating the compatible strings for all families, and load the
> family as an interface dependency (the current approach).
> 3) Introduce a realtek-common module, a module for each interface,
> both repeating the compatible strings for all families, and load the
> family as an interface dependency (the first patch in this series).

As you say below, this is too much for your test device. Skip.

> 4) Introduce a realtek-common module, a module for each interface,
> both repeating the compatible strings for all families, and expect the
> family module to be already loaded.

The kernel should be able to autoload. Skip.

> 5) Introduce a realtek-common module, a module for each interface,
> both repeating the compatible strings for all families, and request
> the family module to be loaded (the last patch in this series).

I had reservations before about this approach and I think I am not
the only one. Let's consider the other options.

> 6) Introduce a realtek-common module, merging everything from
> realtek-smi and realtek-mdio but the driver declarations and implement
> each family as a single module that works both as a platform and an
> mdio driver.
> 7) Introduce a realtek-common module, merging everything from
> realtek-smi and realtek-mdio but the driver declarations and create
> two modules for each family as real drivers, repeating the compatible
> strings on each family (e.g., {rtl8366rb, rtl8365mb}_{smi,mdio}.ko).

Yeah, something like this. Roughly I think we want:

- generic boilerplate probe/remove and regmap setup code in
  realtek-common.c
- interface code in realtek-{smi,mdio}.c
- chip variant code in rtl8365mb.c and rtl8366rb.c

The module dependencies only need to go upwards, i.e.:

       realtek-common
         ^       ^
         |       |  use symbols realtek_common_probe()
         |       |              realtek_common_remove()
         |       |
 realtek-smi   realtek-mdio 
      ^   ^     ^   ^
      |   |     |   |  use symbols realtek_mdio_probe()
      |    \   /    |              realtek_mdio_remove()
      |     \ /     |
      |      X      |  or symbols realtek_smi_probe()
      |     / \     |             realtek_smi_remove()
      |    /   \    |
      |   /     \   |  or both
      |  /       \  |
  rtl8365mb     rtl8366rb

We already see what realtek_common_probe() etc. look like in your patch
series here. If an interface driver needs to do something specific and
it can't do that before or after calling realtek_common_probe(), then it
can be abstracted away into some realtek_common_info struct and the
interface driver can populate this and pass it along.

The interface driver also ought to provide its own probe function that
the chip variant driver can call. Again, if there is something specific
that needs doing in the middle of this process it can call into some
realtek_{smi,mdio}_info function pointer.

I made similar points in my review of your last series so you can look
there for specific instances where I think this is valuable.

After that, the chip variant driver can do some #if ENABLED() logic to
selectively register itself as either a platform driver (SMI), MDIO
driver (MDIO), or both. The MODULE_DEVICE_TABLE is just used by the bus
driver in question (platform or MDIO) to match with the appropriate
driver. Compatible strings are the same, but it's the same chip after
all. In fact, I don't think you even need two MODULE_DEVICE_TABLEs. You
can pass the same one to both the mdio_driver and platform_driver
structs.

I think one needs to look critically at the data being passed around
between the constituent modules at the moment. Some observations:

- for MDIO, the only thing the chip variant driver needs to give it is a
  detect function and its dsa_switch_ops, so why not:

    struct realtek_mdio_info {
      int (*detect)(struct realtek_priv *priv);
      const struct dsa_switch_ops *ds_ops;
    }

- for SMI, some more info is needed:

    struct realtek_smi_info {
      int (*detect)(struct realtek_priv *priv);
      int (*phy_read)(struct realtek_priv *priv, int phy, int regnum);
      int (*phy_write)(struct realtek_priv *priv, int phy, int regnum,
                       u16 val);
      const struct dsa_switch_ops *ds_ops;
    }

  Yes, there is a lot more in struct realtek_ops, but all of that is
  used by rtl8366-core.c. I think this is trivial to disentangle but it
  might result in a fairly large diff if one tries to remove this from
  struct realtek_ops. My point being you don't need to actually populate
  this for the interface driver itself.

With that we can define reasonable signatures for the interface
drivers' probe functions:

  int realtek_smi_probe(struct platform_device *pdev,
                        const struct realtek_smi_info *info);
  int realtek_mdio_probe(struct mdio_device *mdiodev,
                         const struct realtek_mdio_info *info);
  EXPORT_SYMBOL_GPL(both of these functions);

The remove functions can just take the pdev or mdiodev pointers to keep
things balanced.

Now the interface drivers can populate realtek_priv sufficiently for the
realtek_common_probe() to do its job. 

  struct realtek_interface_info {
      static const struct regmap_config *regmap_config;
      static const struct regmap_config *regmap_config_nolock;
      int (*detect)(struct realtek_priv *priv);
      int (*setup_interface)(struct dsa_switch *ds);
      const struct dsa_switch_ops *ds_ops;
  };

The regmap configs are self explanatory.

The setup_interface function pointer is only needed for SMI, and
currently the chip interface drivers are the ones calling it. It is kind
of ugly that this gets passed all the way up, copied into realtek_priv,
and then called all the way down again... there is probably a more
elegant solution. I am mainly trying to illustrate how to handle the
module knot you are trying to unpick.

Now I recall that Vladimir suggested something more along the lines of
realtek_common_probe_pre() and post, and ditto for remove(). You might
prefer that approach. Something to experiment with. You might need
something like that because there are those GPIOs that SMI wants to
fiddle with, and they need to be ready before calling
dsa_register_switch(), and the priv datastructure is not allocated
before calling the common probe function.

Anyway, with the above suggestion I think one can move the vast majority
of common code into realtek-common while keeping the chip variant
drivers mostly unmodified. All that's needed is to move the
MODULE_DEVICE_TABLES into those files and create custom module init/exit
functions which conditionally register themselves as platform and mdio
drivers. This is what the module_platform_driver() and
mdio_module_driver() macros do - they just make the boilerplate
init/exit for you. But nothing stops you from registering both a struct
platform_driver and a struct mdio_driver. Just guard everything with
#ifs to ensure clean compilation.

The relevant probe functions will look like this (rtl8365mb example):

  #if IS_ENABLED(CONFIG_NET_DSA_REALTEK_SMI)
  static const struct realtek_smi_info rtl8365mb_smi_info = {
    .detect = rtl8365mb_detect,
    .phy_read = rtl8365mb_phy_read,
    .phy_write = rtl8365mb_phy_write,
    .ds_ops = rtl8365mb_switch_ops,
  };
  
  int rtl8365mb_smi_probe(struct platform_device *pdev) {
    return realtek_smi_probe(pdev, &rtl8365mb_smi_info);
  }
  #endif
  
  #if IS_ENABLED(CONFIG_NET_DSA_REALTEK_MDIO)
  static const struct realtek_mdio_info rtl8365mb_mdio_info = {
    .detect = rtl8365mb_detect,
    .ds_ops = rtl8365mb_switch_ops,
  };
  
  int rtl8365mb_mdio_probe(struct mdio_device *mdiodev) {
    return realtek_mdio_probe(mdiodev, &rtl8365mb_mdio_info);
  }
  #endif

One thing I dislike is that all the driver private data is getting
allocated way up in the common driver. It would be nicer if the chip
variant or interface drivers could also allocate their own stuff and
just put it in a priv pointer of the parent. Likewise I was never a fan
of the fact that the chip variant drivers always receiving realtek_priv
pointers rather than pointers to their driver private data. You can see
a lot of diving into chip_data especially in rtl8365mb for this
reason. I regret not addressing that before I added the new driver.

> 
> Solutions 1, 2, and 3 may use more resources than needed. My test
> device, for example, cannot handle them. Solution 7 is similar to the
> examples I found in the kernel. Solutions 1 and 6 are the only ones
> not repeating the same compatible strings in different modules, if
> that's a problem.

You will have noticed that with the above solution, the chip variant
modules will invariably require both interface modules to be loaded if
the kernel is built with both REALTEK_SMI and REALTEK_MDIO. I hope that
your resource-constrained system has enough headroom to accommodate
that. If not then I am afraid you might simply be out of luck.

Kind regards,
Alvin
Dan Carpenter Nov. 25, 2023, 7:10 a.m. UTC | #10
Hi Luiz,

kernel test robot noticed the following build warnings:

url:    https://github.com/intel-lab-lkp/linux/commits/Luiz-Angelo-Daros-de-Luca/net-dsa-realtek-create-realtek-common/20231118-075444
base:   net-next/main
patch link:    https://lore.kernel.org/r/20231117235140.1178-3-luizluca%40gmail.com
patch subject: [net-next 2/2] net: dsa: realtek: load switch variants on demand
config: mips-randconfig-r081-20231121 (https://download.01.org/0day-ci/archive/20231125/202311251132.QKdGl71R-lkp@intel.com/config)
compiler: mips-linux-gcc (GCC) 13.2.0
reproduce: (https://download.01.org/0day-ci/archive/20231125/202311251132.QKdGl71R-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>
| Reported-by: Dan Carpenter <error27@gmail.com>
| Closes: https://lore.kernel.org/r/202311251132.QKdGl71R-lkp@intel.com/

smatch warnings:
drivers/net/dsa/realtek/realtek-smi.c:418 realtek_smi_probe() warn: passing zero to 'PTR_ERR'

vim +/PTR_ERR +418 drivers/net/dsa/realtek/realtek-smi.c

d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  398  static int realtek_smi_probe(struct platform_device *pdev)
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  399  {
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  400  	struct device *dev = &pdev->dev;
f5f119077b1cd6 drivers/net/dsa/realtek/realtek-smi-core.c Luiz Angelo Daros de Luca 2022-01-28  401  	struct realtek_priv *priv;
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  402  	int ret;
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  403  
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  404  	priv = realtek_common_probe(dev, realtek_smi_regmap_config,
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  405  				    realtek_smi_nolock_regmap_config);
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  406  	if (IS_ERR(priv))
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  407  		return PTR_ERR(priv);
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  408  
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  409  	/* Fetch MDIO pins */
f5f119077b1cd6 drivers/net/dsa/realtek/realtek-smi-core.c Luiz Angelo Daros de Luca 2022-01-28  410  	priv->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW);
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  411  	if (IS_ERR(priv->mdc)) {
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  412  		ret = PTR_ERR(priv->mdc);
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  413  		goto err_variant_put;
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  414  	}
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  415  
f5f119077b1cd6 drivers/net/dsa/realtek/realtek-smi-core.c Luiz Angelo Daros de Luca 2022-01-28  416  	priv->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW);
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  417  	if (IS_ERR(priv->mdio)) {
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17 @418  		ret = PTR_ERR(priv->mdc);

s/mdc/mdio/.

7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  419  		goto err_variant_put;
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  420  	}
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  421  
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  422  	priv->setup_interface = realtek_smi_setup_mdio;
217d45f6e61f5d drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  423  	priv->write_reg_noack = realtek_smi_write_reg_noack;
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  424  
f5f119077b1cd6 drivers/net/dsa/realtek/realtek-smi-core.c Luiz Angelo Daros de Luca 2022-01-28  425  	ret = priv->ops->detect(priv);
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  426  	if (ret) {
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  427  		dev_err(dev, "unable to detect switch\n");
7e61e799f3f92c drivers/net/dsa/realtek/realtek-smi.c      Luiz Angelo Daros de Luca 2023-11-17  428  		goto err_variant_put;
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  429  	}
d8652956cf37c5 drivers/net/dsa/realtek-smi.c              Linus Walleij             2018-07-14  430
Luiz Angelo Daros de Luca Nov. 27, 2023, 10:24 p.m. UTC | #11
Hi Alvin,

> > 4) Introduce a realtek-common module, a module for each interface,
> > both repeating the compatible strings for all families, and expect the
> > family module to be already loaded.
>
> The kernel should be able to autoload. Skip.
>
> > 5) Introduce a realtek-common module, a module for each interface,
> > both repeating the compatible strings for all families, and request
> > the family module to be loaded (the last patch in this series).
>
> I had reservations before about this approach and I think I am not
> the only one. Let's consider the other options.

I'm not sure if getting/putting a module is a problem or if I can
request it when missing. I would like some options on that specific
topic from the experts. It seems to happen in many places, even in DSA
tag code.

> > 6) Introduce a realtek-common module, merging everything from
> > realtek-smi and realtek-mdio but the driver declarations and implement
> > each family as a single module that works both as a platform and an
> > mdio driver.
> > 7) Introduce a realtek-common module, merging everything from
> > realtek-smi and realtek-mdio but the driver declarations and create
> > two modules for each family as real drivers, repeating the compatible
> > strings on each family (e.g., {rtl8366rb, rtl8365mb}_{smi,mdio}.ko).
>
> Yeah, something like this. Roughly I think we want:

Just to be sure, you are suggesting something like 6), not 7), right?

> - generic boilerplate probe/remove and regmap setup code in
>   realtek-common.c
> - interface code in realtek-{smi,mdio}.c
> - chip variant code in rtl8365mb.c and rtl8366rb.c
>
> The module dependencies only need to go upwards, i.e.:
>
>        realtek-common
>          ^       ^
>          |       |  use symbols realtek_common_probe()
>          |       |              realtek_common_remove()
>          |       |
>  realtek-smi   realtek-mdio
>       ^   ^     ^   ^
>       |   |     |   |  use symbols realtek_mdio_probe()
>       |    \   /    |              realtek_mdio_remove()
>       |     \ /     |
>       |      X      |  or symbols realtek_smi_probe()
>       |     / \     |             realtek_smi_remove()
>       |    /   \    |
>       |   /     \   |  or both
>       |  /       \  |
>   rtl8365mb     rtl8366rb

It makes sense to turn rtl8365mb/rtl8366rb into both a platform and an
MDIO driver, similar to the merge between realtek-mdio/realtek-smi
Vladmir suggested before, with a custom module_init/exit doing the
job. I didn't invest too much time thinking about how data structures
would fit in this new model. realtek_priv would probably be allocated
by the variant modules with little left for interface to probe/setup.

> The setup_interface function pointer is only needed for SMI, and
> currently the chip interface drivers are the ones calling it. It is kind
> of ugly that this gets passed all the way up, copied into realtek_priv,
> and then called all the way down again... there is probably a more
> elegant solution. I am mainly trying to illustrate how to handle the
> module knot you are trying to unpick.

The "realtek_smi_setup_mdio()" used in setup_interface isn't really
necessary (like it happens in realtek-mdio). It could be used (or not)
by both interfaces. The "realtek,smi-mdio" compatible string is
misleading, indicating it might be something specific to the SMI
interface HW while it is just how the driver was implemented. A
"realtek,slave_mdio" or "realtek,user_mii" would be better.

I believe the strange relations between realtek interface modules and
variants tend to go away if we put things in the right place. The
probe will happen mostly in common and variant modules, leaving just a
minor probe job for the interface module called from the variant
driver (using pre/post approach) or from common (using a
realtek_interface_ops). Anyway, I'll leave the discussion of who calls
who after we settle the role of each module.

The most likely proposal is to convert both variant modules from a
subdriver code into a both platform(for SMI) and mdio driver. Probe
will use both realtek_<interface>_probe() and realtek_common_probe()
when appropriated. Variants will call the interface and not the other
way around.

> > Solutions 1, 2, and 3 may use more resources than needed. My test
> > device, for example, cannot handle them. Solution 7 is similar to the
> > examples I found in the kernel. Solutions 1 and 6 are the only ones
> > not repeating the same compatible strings in different modules, if
> > that's a problem.
>
> You will have noticed that with the above solution, the chip variant
> modules will invariably require both interface modules to be loaded if
> the kernel is built with both REALTEK_SMI and REALTEK_MDIO. I hope that
> your resource-constrained system has enough headroom to accommodate
> that. If not then I am afraid you might simply be out of luck.

I wouldn't say it will invariably require both interface modules to be
loaded. The dynamic load would be much simpler if variants request the
interface module as we only have two (at most 3 with a future
realtek-spi) modules. We would just need to call a
realtek_interface_get() and realtek_interface_put() on each respective
probe. The module names will be well-known with no issues with
module_alias.

Thanks for your help, Alvin. I'll wait for a couple of more days for
others to manifest.

Regards,

Luiz
Vladimir Oltean Dec. 7, 2023, 5:02 p.m. UTC | #12
On Mon, Nov 27, 2023 at 07:24:16PM -0300, Luiz Angelo Daros de Luca wrote:
> I'm not sure if getting/putting a module is a problem or if I can
> request it when missing. I would like some options on that specific
> topic from the experts. It seems to happen in many places, even in DSA
> tag code.
> 
> I wouldn't say it will invariably require both interface modules to be
> loaded. The dynamic load would be much simpler if variants request the
> interface module as we only have two (at most 3 with a future
> realtek-spi) modules. We would just need to call a
> realtek_interface_get() and realtek_interface_put() on each respective
> probe. The module names will be well-known with no issues with
> module_alias.
> 
> Thanks for your help, Alvin. I'll wait for a couple of more days for
> others to manifest.

I'm not an expert on this topic either, but Alvin's suggestion makes
sense to have the switch variant drivers be both platform and MDIO
device drivers, and call symbols exported by the interface drivers as
needed.

If you are able to make the variant driver depend on just the interface
driver in use based on some request_module() calls, I don't think that
will be a problem with Krzysztof either, since he just said to not
duplicate the MODULE_DEVICE_TABLE() functionality.

I think it's down to prototyping something and seeing what are the pros
and cons.
Vladimir Oltean Dec. 7, 2023, 5:19 p.m. UTC | #13
On Mon, Nov 27, 2023 at 07:24:16PM -0300, Luiz Angelo Daros de Luca wrote:
> The "realtek_smi_setup_mdio()" used in setup_interface isn't really
> necessary (like it happens in realtek-mdio). It could be used (or not)
> by both interfaces. The "realtek,smi-mdio" compatible string is
> misleading, indicating it might be something specific to the SMI
> interface HW while it is just how the driver was implemented. A
> "realtek,slave_mdio" or "realtek,user_mii" would be better.

The compatible string is about picking a driver for a device. It is
supposed to uniquely describe the register layout and functionality of
that IP block, not its functional role in the kernel. "slave_mdio" and
"user_mii" are too ingrained with "this MDIO controller gives access to
internal PHY ports of DSA slave ports".

Even if the MDIO controller doesn't currently have its own struct device
and driver, you'd have to think of the fact that it could, when picking
an appropriate compatible string.

If you have very specific information that the MDIO controller is based on
some reusable/licensable IP block and there were no modifications made
to it, you could use that compatible string.

Otherwise, another sensible choice would be "realtek,<precise-soc-name>-mdio",
because it leaves room for future extensions with other compatible
strings, more generic or just as specific, that all bind to the same
driver.

It's always good to start being too specific rather than too generic,
because a future Realtek switch might have a different IP block for its
MDIO controller. Then a driver for your existing "realtek,smi-mdio" or
"realtek,slave_mdio" or "realtek,user_mii" compatible string sounds like
it could handle it, but it can't.

You can always add secondary compatible strings to a node, but changing
the existing one breaks the ABI between the kernel and the DT.
Luiz Angelo Daros de Luca Dec. 7, 2023, 7:50 p.m. UTC | #14
> On Mon, Nov 27, 2023 at 07:24:16PM -0300, Luiz Angelo Daros de Luca wrote:
> > The "realtek_smi_setup_mdio()" used in setup_interface isn't really
> > necessary (like it happens in realtek-mdio). It could be used (or not)
> > by both interfaces. The "realtek,smi-mdio" compatible string is
> > misleading, indicating it might be something specific to the SMI
> > interface HW while it is just how the driver was implemented. A
> > "realtek,slave_mdio" or "realtek,user_mii" would be better.
>
> The compatible string is about picking a driver for a device. It is
> supposed to uniquely describe the register layout and functionality of
> that IP block, not its functional role in the kernel. "slave_mdio" and
> "user_mii" are too ingrained with "this MDIO controller gives access to
> internal PHY ports of DSA slave ports".
>
> Even if the MDIO controller doesn't currently have its own struct device
> and driver, you'd have to think of the fact that it could, when picking
> an appropriate compatible string.
>
> If you have very specific information that the MDIO controller is based on
> some reusable/licensable IP block and there were no modifications made
> to it, you could use that compatible string.
>
> Otherwise, another sensible choice would be "realtek,<precise-soc-name>-mdio",
> because it leaves room for future extensions with other compatible
> strings, more generic or just as specific, that all bind to the same
> driver.
>
> It's always good to start being too specific rather than too generic,
> because a future Realtek switch might have a different IP block for its
> MDIO controller. Then a driver for your existing "realtek,smi-mdio" or
> "realtek,slave_mdio" or "realtek,user_mii" compatible string sounds like
> it could handle it, but it can't.
>
> You can always add secondary compatible strings to a node, but changing
> the existing one breaks the ABI between the kernel and the DT.

HI Vladmir,

We discussed something about that in the past:

https://lkml.kernel.org/netdev/20220630200423.tieprdu5fpabflj7@bang-olufsen.dk/T/#m04e6cf7d0c1f18b02c2bf40266ada915b1f02f3d

The code is able to handle only a single node and binding docs say it
should be named "mdio". The compatible string wasn't a requirement
since the beginning and I don't think it is worth it to rename the
compatible string. I suggest we simply switch to
of_get_child_by_name() and look for a node named "mdio". If that node
is not found, we can still look for the old compatible string
(backwards compatibility) and probably warn the "user" (targeting not
the end-user but the one creating the DT for a new device).

I don't know how to handle the binding docs as the compatible string
is still a requirement for older kernel versions. Is it ok to update
the device-tree bindings docs in such a way it would break old
drivers? Or should we keep it there until the last LTS kernel
requiring it reaches EOL? As device-tree bindings docs should not
consider how the driver was implemented, I think it would be strange
to have a note like "required by kernel up to 6.x".

Regards,

Luiz
Luiz Angelo Daros de Luca Dec. 7, 2023, 8:22 p.m. UTC | #15
Hi Vladimir,

> > I'm not sure if getting/putting a module is a problem or if I can
> > request it when missing. I would like some options on that specific
> > topic from the experts. It seems to happen in many places, even in DSA
> > tag code.
> >
> > I wouldn't say it will invariably require both interface modules to be
> > loaded. The dynamic load would be much simpler if variants request the
> > interface module as we only have two (at most 3 with a future
> > realtek-spi) modules. We would just need to call a
> > realtek_interface_get() and realtek_interface_put() on each respective
> > probe. The module names will be well-known with no issues with
> > module_alias.
> >
> > Thanks for your help, Alvin. I'll wait for a couple of more days for
> > others to manifest.
>
> I'm not an expert on this topic either, but Alvin's suggestion makes
> sense to have the switch variant drivers be both platform and MDIO
> device drivers, and call symbols exported by the interface drivers as
> needed.

Yes, it does. It looks like the driver was upside down.

> If you are able to make the variant driver depend on just the interface
> driver in use based on some request_module() calls, I don't think that
> will be a problem with Krzysztof either, since he just said to not
> duplicate the MODULE_DEVICE_TABLE() functionality.

The interface modules are quite small, multiple times smaller than the
variant module. It wasn't worth it to load them on demand as the code
to handle that might be close to the interface module size. Indeed, as
we'll have a common module, I think the best solution would be to
merge both interfaces into the common module. It would make things
much simpler: two variant/families modules that require a single
common module. It is also closer to what we see in other DSA drivers.

> I think it's down to prototyping something and seeing what are the pros
> and cons.

I already did that and I'm finishing some tests before submitting it.
It looks like it fits nicely. I avoided some struct refactoring Alvim
suggested to keep the change as small as possible but I went a little
further migrating the user mdio driver to common and use it for both
interfaces.

Regards,

Luiz
Vladimir Oltean Dec. 7, 2023, 10:31 p.m. UTC | #16
On Thu, Dec 07, 2023 at 04:50:12PM -0300, Luiz Angelo Daros de Luca wrote:
> HI Vladmir,
> 
> We discussed something about that in the past:
> 
> https://lkml.kernel.org/netdev/20220630200423.tieprdu5fpabflj7@bang-olufsen.dk/T/#m04e6cf7d0c1f18b02c2bf40266ada915b1f02f3d
> 
> The code is able to handle only a single node and binding docs say it
> should be named "mdio". The compatible string wasn't a requirement
> since the beginning and I don't think it is worth it to rename the
> compatible string. I suggest we simply switch to
> of_get_child_by_name() and look for a node named "mdio". If that node
> is not found, we can still look for the old compatible string
> (backwards compatibility) and probably warn the "user" (targeting not
> the end-user but the one creating the DT for a new device).
> 
> I don't know how to handle the binding docs as the compatible string
> is still a requirement for older kernel versions. Is it ok to update
> the device-tree bindings docs in such a way it would break old
> drivers? Or should we keep it there until the last LTS kernel
> requiring it reaches EOL? As device-tree bindings docs should not
> consider how the driver was implemented, I think it would be strange
> to have a note like "required by kernel up to 6.x".
> 
> Regards,
> 
> Luiz

And did you ever answer this question?

"And why do you even need to remove the compatible string from the MDIO
node, can't you just ignore it, does it bother you in any way?"

I'm very confused as to what you're after.
Luiz Angelo Daros de Luca Dec. 8, 2023, 2:46 a.m. UTC | #17
> > We discussed something about that in the past:
> >
> > https://lkml.kernel.org/netdev/20220630200423.tieprdu5fpabflj7@bang-olufsen.dk/T/#m04e6cf7d0c1f18b02c2bf40266ada915b1f02f3d
> >
> > The code is able to handle only a single node and binding docs say it
> > should be named "mdio". The compatible string wasn't a requirement
> > since the beginning and I don't think it is worth it to rename the
> > compatible string. I suggest we simply switch to
> > of_get_child_by_name() and look for a node named "mdio". If that node
> > is not found, we can still look for the old compatible string
> > (backwards compatibility) and probably warn the "user" (targeting not
> > the end-user but the one creating the DT for a new device).
> >
> > I don't know how to handle the binding docs as the compatible string
> > is still a requirement for older kernel versions. Is it ok to update
> > the device-tree bindings docs in such a way it would break old
> > drivers? Or should we keep it there until the last LTS kernel
> > requiring it reaches EOL? As device-tree bindings docs should not
> > consider how the driver was implemented, I think it would be strange
> > to have a note like "required by kernel up to 6.x".
> >
> > Regards,
> >
> > Luiz
>
> And did you ever answer this question?
>
> "And why do you even need to remove the compatible string from the MDIO
> node, can't you just ignore it, does it bother you in any way?"
>
> I'm very confused as to what you're after.

The device-tree bindings should delineate the hardware characteristics
rather than specifying the implementation details of a particular
driver. The requirement of an "mdio" node with a compatible string
such as "realtek,smi-mdio" may be misleading, implying a potential
correlation between the host-switch interface (SMI, SPI, or MDIO) and
a specific user MDIO it describes. It's important to note that how we
describe the user mdio could vary for other future switch families,
but not with a distinct management interface.

I am currently conducting tests using the same user MDIO driver for
both realtek-smi and realtek-mdio. However, it's noteworthy that
unlike realtek-smi, the current user MDIO for realtek-mdio does not
require a compatible string; only a node named "mdio". Realtek-mdio is
presently utilizing the generic DSA user MDIO, but you mentioned it's
not considered a "core functionality." I assume this implies I
shouldn't depend on it. That's the reason for my switch to the
existing user MDIO driver from realtek-smi.

Regarding the absence of a compatible string for realtek-mdio, we have
a few options: introducing a new compatible string exclusively for
realtek-mdio, such as "realtek,mdio-mdio"; creating a new generic one
for both interfaces like "realtek,user-mdio" or "rtl836x-user-mdio";
or simply ignore the compatible string, as you suggested. However, if
I opt to ignore it, I presume I should retrieve that node solely based
on the node name. That's what I'm after. Is my understanding correct?

I'll post a new series that is still compatible both with old HW
descriptions and the device-tree bindings. In that way, I'll not touch
the docs. However, given that the compatible string is unnecessary to
describe the hardware, and after we modify the code to disregard it,
it is awkward for the binding documentation to request a compatible
string that serves no purpose. Shouldn't we consider updating this
requirement at some point?

Regards,

Luiz
Vladimir Oltean Dec. 11, 2023, 6:01 p.m. UTC | #18
On Thu, Dec 07, 2023 at 11:46:46PM -0300, Luiz Angelo Daros de Luca wrote:
> The device-tree bindings should delineate the hardware characteristics
> rather than specifying the implementation details of a particular
> driver. The requirement of an "mdio" node with a compatible string
> such as "realtek,smi-mdio" may be misleading, implying a potential
> correlation between the host-switch interface (SMI, SPI, or MDIO) and
> a specific user MDIO it describes. It's important to note that how we
> describe the user mdio could vary for other future switch families,
> but not with a distinct management interface.

Agree, "realtek,smi-mdio" is not a great compatible string. But it is an
established compatible string.

> I am currently conducting tests using the same user MDIO driver for
> both realtek-smi and realtek-mdio. However, it's noteworthy that
> unlike realtek-smi, the current user MDIO for realtek-mdio does not
> require a compatible string; only a node named "mdio". Realtek-mdio is
> presently utilizing the generic DSA user MDIO, but you mentioned it's
> not considered a "core functionality." I assume this implies I
> shouldn't depend on it. That's the reason for my switch to the
> existing user MDIO driver from realtek-smi.
> 
> Regarding the absence of a compatible string for realtek-mdio, we have
> a few options: introducing a new compatible string exclusively for
> realtek-mdio, such as "realtek,mdio-mdio"; creating a new generic one
> for both interfaces like "realtek,user-mdio" or "rtl836x-user-mdio";

The naming choice really looks like the secondary problem here.
But what about "realtek,rtl8365mb-mdio" and "realtek,rtl8366rb-mdio" as
a secondary compatible string for SMI, and primary compatible string for
MDIO-connected switches?

> or simply ignore the compatible string, as you suggested. However, if
> I opt to ignore it, I presume I should retrieve that node solely based
> on the node name. That's what I'm after. Is my understanding correct?

You could do that. There's a very high chance that the node was named
"mdio". The schema says it should be called "mdio", _and_ be compatible
with realtek,smi-mdio. If anyone comes and complains (very unlikely),
just say "hey, even Documentation/devicetree/bindings/net/dsa/realtek-smi.txt
said you should name the node 'mdio'"!

> I'll post a new series that is still compatible both with old HW
> descriptions and the device-tree bindings. In that way, I'll not touch
> the docs.

> However, given that the compatible string is unnecessary to describe
> the hardware, and after we modify the code to disregard it, it is
> awkward for the binding documentation to request a compatible string
> that serves no purpose. Shouldn't we consider updating this
> requirement at some point?
> 
> Regards,
> 
> Luiz

Not everything that is in the device tree has to be used. It is a
description of the hardware, not a rigid set of instructions for what
the OS has to do. The OS still does whatever it wants based on that info.

You can ask anyone about this. See Thomas Petazzoni's slides as just one
example.
https://elinux.org/images/f/f9/Petazzoni-device-tree-dummies_0.pdf

| The Device Tree is really a hardware description language.
| It should describe the hardware layout, and how it works.
| But it should not describe which particular hardware configuration you’re interested in.
| As an example:
| You may describe in the DT whether a particular piece of hardware supports DMA or not.
| But you may not describe in the DT if you want to use DMA or not.

It's a really weak argument for recommending users to remove the compatible
string, thereby deliberately breaking ABI compatibility in one direction,
to literally _no_ benefit.

Compatible strings for MDIO controllers are, in essence, not strange.
They are independent devices which could reasonably be bound to their
own drivers. I don't see what's awkward about this.
diff mbox series

Patch

diff --git a/drivers/net/dsa/realtek/realtek-common.c b/drivers/net/dsa/realtek/realtek-common.c
index 1b733ac56560..cdd8d77c20d9 100644
--- a/drivers/net/dsa/realtek/realtek-common.c
+++ b/drivers/net/dsa/realtek/realtek-common.c
@@ -1,10 +1,72 @@ 
 // SPDX-License-Identifier: GPL-2.0+
 
 #include <linux/module.h>
+#include <linux/of_device.h>
 
 #include "realtek.h"
 #include "realtek-common.h"
 
+static LIST_HEAD(realtek_variants_list);
+static DEFINE_MUTEX(realtek_variants_lock);
+
+void realtek_variant_register(struct realtek_variant_entry *variant_entry)
+{
+	mutex_lock(&realtek_variants_lock);
+	list_add_tail(&variant_entry->list, &realtek_variants_list);
+	mutex_unlock(&realtek_variants_lock);
+}
+EXPORT_SYMBOL_GPL(realtek_variant_register);
+
+void realtek_variant_unregister(struct realtek_variant_entry *variant_entry)
+{
+	mutex_lock(&realtek_variants_lock);
+	list_del(&variant_entry->list);
+	mutex_unlock(&realtek_variants_lock);
+}
+EXPORT_SYMBOL_GPL(realtek_variant_unregister);
+
+const struct realtek_variant *realtek_variant_get(const char *compatible)
+{
+	const struct realtek_variant *variant = ERR_PTR(-ENOENT);
+	struct realtek_variant_entry *variant_entry;
+
+	request_module(compatible);
+
+	mutex_lock(&realtek_variants_lock);
+	list_for_each_entry(variant_entry, &realtek_variants_list, list) {
+		if (strcmp(compatible, variant_entry->compatible))
+			continue;
+
+		if (!try_module_get(variant_entry->owner))
+			break;
+
+		variant = variant_entry->variant;
+		break;
+	}
+	mutex_unlock(&realtek_variants_lock);
+
+	return variant;
+}
+EXPORT_SYMBOL_GPL(realtek_variant_get);
+
+void realtek_variant_put(const struct realtek_variant *var)
+{
+	struct realtek_variant_entry *variant_entry;
+
+	mutex_lock(&realtek_variants_lock);
+	list_for_each_entry(variant_entry, &realtek_variants_list, list) {
+		if (variant_entry->variant != var)
+			continue;
+
+		if (variant_entry->owner)
+			module_put(variant_entry->owner);
+
+		break;
+	}
+	mutex_unlock(&realtek_variants_lock);
+}
+EXPORT_SYMBOL_GPL(realtek_variant_put);
+
 void realtek_common_lock(void *ctx)
 {
 	struct realtek_priv *priv = ctx;
@@ -25,18 +87,30 @@  struct realtek_priv *realtek_common_probe(struct device *dev,
 		struct regmap_config rc, struct regmap_config rc_nolock)
 {
 	const struct realtek_variant *var;
+	const struct of_device_id *match;
 	struct realtek_priv *priv;
 	struct device_node *np;
 	int ret;
 
-	var = of_device_get_match_data(dev);
-	if (!var)
+	match = of_match_device(dev->driver->of_match_table, dev);
+	if (!match)
 		return ERR_PTR(-EINVAL);
 
+	var = realtek_variant_get(match->compatible);
+	if (IS_ERR(var)) {
+		ret = PTR_ERR(var);
+		dev_err_probe(dev, ret,
+			      "failed to get module for alias '%s'",
+			      match->compatible);
+		goto err_variant_put;
+	}
+
 	priv = devm_kzalloc(dev, size_add(sizeof(*priv), var->chip_data_sz),
 			    GFP_KERNEL);
-	if (!priv)
-		return ERR_PTR(-ENOMEM);
+	if (!priv) {
+		ret = -ENOMEM;
+		goto err_variant_put;
+	}
 
 	mutex_init(&priv->map_lock);
 
@@ -45,14 +119,14 @@  struct realtek_priv *realtek_common_probe(struct device *dev,
 	if (IS_ERR(priv->map)) {
 		ret = PTR_ERR(priv->map);
 		dev_err(dev, "regmap init failed: %d\n", ret);
-		return ERR_PTR(ret);
+		goto err_variant_put;
 	}
 
 	priv->map_nolock = devm_regmap_init(dev, NULL, priv, &rc_nolock);
 	if (IS_ERR(priv->map_nolock)) {
 		ret = PTR_ERR(priv->map_nolock);
 		dev_err(dev, "regmap init failed: %d\n", ret);
-		return ERR_PTR(ret);
+		goto err_variant_put;
 	}
 
 	/* Link forward and backward */
@@ -69,11 +143,11 @@  struct realtek_priv *realtek_common_probe(struct device *dev,
 	priv->leds_disabled = of_property_read_bool(np, "realtek,disable-leds");
 
 	/* TODO: if power is software controlled, set up any regulators here */
-
 	priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
 	if (IS_ERR(priv->reset)) {
+		ret = PTR_ERR(priv->reset);
 		dev_err(dev, "failed to get RESET GPIO\n");
-		return ERR_CAST(priv->reset);
+		goto err_variant_put;
 	}
 	if (priv->reset) {
 		gpiod_set_value(priv->reset, 1);
@@ -85,13 +159,20 @@  struct realtek_priv *realtek_common_probe(struct device *dev,
 	}
 
 	priv->ds = devm_kzalloc(dev, sizeof(*priv->ds), GFP_KERNEL);
-	if (!priv->ds)
-		return ERR_PTR(-ENOMEM);
+	if (!priv->ds) {
+		ret = -ENOMEM;
+		goto err_variant_put;
+	}
 
 	priv->ds->dev = dev;
 	priv->ds->priv = priv;
 
 	return priv;
+
+err_variant_put:
+	realtek_variant_put(var);
+
+	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL(realtek_common_probe);
 
@@ -104,6 +185,8 @@  void realtek_common_remove(struct realtek_priv *priv)
 	if (priv->user_mii_bus)
 		of_node_put(priv->user_mii_bus->dev.of_node);
 
+	realtek_variant_put(priv->variant);
+
 	/* leave the device reset asserted */
 	if (priv->reset)
 		gpiod_set_value(priv->reset, 1);
@@ -112,10 +195,10 @@  EXPORT_SYMBOL(realtek_common_remove);
 
 const struct of_device_id realtek_common_of_match[] = {
 #if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8366RB)
-	{ .compatible = "realtek,rtl8366rb", .data = &rtl8366rb_variant, },
+	{ .compatible = "realtek,rtl8366rb", },
 #endif
 #if IS_ENABLED(CONFIG_NET_DSA_REALTEK_RTL8365MB)
-	{ .compatible = "realtek,rtl8365mb", .data = &rtl8365mb_variant, },
+	{ .compatible = "realtek,rtl8365mb", },
 #endif
 	{ /* sentinel */ },
 };
diff --git a/drivers/net/dsa/realtek/realtek-common.h b/drivers/net/dsa/realtek/realtek-common.h
index 90a949386518..6de4991d8b5c 100644
--- a/drivers/net/dsa/realtek/realtek-common.h
+++ b/drivers/net/dsa/realtek/realtek-common.h
@@ -5,6 +5,37 @@ 
 
 #include <linux/regmap.h>
 
+struct realtek_variant_entry {
+	const struct realtek_variant *variant;
+	const char *compatible;
+	struct module *owner;
+	struct list_head list;
+};
+
+#define module_realtek_variant(__variant, __compatible)			\
+static struct realtek_variant_entry __variant ## _entry = {		\
+	.compatible = __compatible,					\
+	.variant = &(__variant),					\
+	.owner = THIS_MODULE,						\
+};									\
+static int __init realtek_variant_module_init(void)			\
+{									\
+	realtek_variant_register(&__variant ## _entry);			\
+	return 0;							\
+}									\
+module_init(realtek_variant_module_init)				\
+									\
+static void __exit realtek_variant_module_exit(void)			\
+{									\
+	realtek_variant_unregister(&__variant ## _entry);		\
+}									\
+module_exit(realtek_variant_module_exit);				\
+									\
+MODULE_ALIAS(__compatible)
+
+void realtek_variant_register(struct realtek_variant_entry *variant_entry);
+void realtek_variant_unregister(struct realtek_variant_entry *variant_entry);
+
 extern const struct of_device_id realtek_common_of_match[];
 
 void realtek_common_lock(void *ctx);
@@ -12,5 +43,7 @@  void realtek_common_unlock(void *ctx);
 struct realtek_priv *realtek_common_probe(struct device *dev,
 		struct regmap_config rc, struct regmap_config rc_nolock);
 void realtek_common_remove(struct realtek_priv *priv);
+const struct realtek_variant *realtek_variant_get(const char *compatible);
+void realtek_variant_put(const struct realtek_variant *var);
 
 #endif
diff --git a/drivers/net/dsa/realtek/realtek-mdio.c b/drivers/net/dsa/realtek/realtek-mdio.c
index b865e11955ca..c447dd815a59 100644
--- a/drivers/net/dsa/realtek/realtek-mdio.c
+++ b/drivers/net/dsa/realtek/realtek-mdio.c
@@ -145,7 +145,7 @@  static int realtek_mdio_probe(struct mdio_device *mdiodev)
 	ret = priv->ops->detect(priv);
 	if (ret) {
 		dev_err(dev, "unable to detect switch\n");
-		return ret;
+		goto err_variant_put;
 	}
 
 	priv->ds->ops = priv->variant->ds_ops_mdio;
@@ -154,10 +154,15 @@  static int realtek_mdio_probe(struct mdio_device *mdiodev)
 	ret = dsa_register_switch(priv->ds);
 	if (ret) {
 		dev_err_probe(dev, ret, "unable to register switch\n");
-		return ret;
+		goto err_variant_put;
 	}
 
 	return 0;
+
+err_variant_put:
+	realtek_variant_put(priv->variant);
+
+	return ret;
 }
 
 static void realtek_mdio_remove(struct mdio_device *mdiodev)
diff --git a/drivers/net/dsa/realtek/realtek-smi.c b/drivers/net/dsa/realtek/realtek-smi.c
index 2aebcbe0425f..e50b3c6203e6 100644
--- a/drivers/net/dsa/realtek/realtek-smi.c
+++ b/drivers/net/dsa/realtek/realtek-smi.c
@@ -408,12 +408,16 @@  static int realtek_smi_probe(struct platform_device *pdev)
 
 	/* Fetch MDIO pins */
 	priv->mdc = devm_gpiod_get_optional(dev, "mdc", GPIOD_OUT_LOW);
-	if (IS_ERR(priv->mdc))
-		return PTR_ERR(priv->mdc);
+	if (IS_ERR(priv->mdc)) {
+		ret = PTR_ERR(priv->mdc);
+		goto err_variant_put;
+	}
 
 	priv->mdio = devm_gpiod_get_optional(dev, "mdio", GPIOD_OUT_LOW);
-	if (IS_ERR(priv->mdio))
-		return PTR_ERR(priv->mdio);
+	if (IS_ERR(priv->mdio)) {
+		ret = PTR_ERR(priv->mdc);
+		goto err_variant_put;
+	}
 
 	priv->setup_interface = realtek_smi_setup_mdio;
 	priv->write_reg_noack = realtek_smi_write_reg_noack;
@@ -421,7 +425,7 @@  static int realtek_smi_probe(struct platform_device *pdev)
 	ret = priv->ops->detect(priv);
 	if (ret) {
 		dev_err(dev, "unable to detect switch\n");
-		return ret;
+		goto err_variant_put;
 	}
 
 	priv->ds->ops = priv->variant->ds_ops_smi;
@@ -430,10 +434,15 @@  static int realtek_smi_probe(struct platform_device *pdev)
 	ret = dsa_register_switch(priv->ds);
 	if (ret) {
 		dev_err_probe(dev, ret, "unable to register switch\n");
-		return ret;
+		goto err_variant_put;
 	}
 
 	return 0;
+
+err_variant_put:
+	realtek_variant_put(priv->variant);
+
+	return ret;
 }
 
 static void realtek_smi_remove(struct platform_device *pdev)
diff --git a/drivers/net/dsa/realtek/realtek.h b/drivers/net/dsa/realtek/realtek.h
index fbbdf538908e..267a1dc02080 100644
--- a/drivers/net/dsa/realtek/realtek.h
+++ b/drivers/net/dsa/realtek/realtek.h
@@ -143,7 +143,4 @@  void rtl8366_get_strings(struct dsa_switch *ds, int port, u32 stringset,
 int rtl8366_get_sset_count(struct dsa_switch *ds, int port, int sset);
 void rtl8366_get_ethtool_stats(struct dsa_switch *ds, int port, uint64_t *data);
 
-extern const struct realtek_variant rtl8366rb_variant;
-extern const struct realtek_variant rtl8365mb_variant;
-
 #endif /*  _REALTEK_H */
diff --git a/drivers/net/dsa/realtek/rtl8365mb.c b/drivers/net/dsa/realtek/rtl8365mb.c
index 9b18774e988c..fb214dd717f0 100644
--- a/drivers/net/dsa/realtek/rtl8365mb.c
+++ b/drivers/net/dsa/realtek/rtl8365mb.c
@@ -2164,7 +2164,7 @@  static const struct realtek_ops rtl8365mb_ops = {
 	.phy_write = rtl8365mb_phy_write,
 };
 
-const struct realtek_variant rtl8365mb_variant = {
+static const struct realtek_variant rtl8365mb_variant = {
 	.ds_ops_smi = &rtl8365mb_switch_ops_smi,
 	.ds_ops_mdio = &rtl8365mb_switch_ops_mdio,
 	.ops = &rtl8365mb_ops,
@@ -2173,7 +2173,7 @@  const struct realtek_variant rtl8365mb_variant = {
 	.cmd_write = 0xb8,
 	.chip_data_sz = sizeof(struct rtl8365mb),
 };
-EXPORT_SYMBOL_GPL(rtl8365mb_variant);
+module_realtek_variant(rtl8365mb_variant, "realtek,rtl8365mb");
 
 MODULE_AUTHOR("Alvin Šipraga <alsi@bang-olufsen.dk>");
 MODULE_DESCRIPTION("Driver for RTL8365MB-VC ethernet switch");
diff --git a/drivers/net/dsa/realtek/rtl8366rb.c b/drivers/net/dsa/realtek/rtl8366rb.c
index 1ac2fd098242..143c57c69ace 100644
--- a/drivers/net/dsa/realtek/rtl8366rb.c
+++ b/drivers/net/dsa/realtek/rtl8366rb.c
@@ -1912,7 +1912,7 @@  static const struct realtek_ops rtl8366rb_ops = {
 	.phy_write	= rtl8366rb_phy_write,
 };
 
-const struct realtek_variant rtl8366rb_variant = {
+static const struct realtek_variant rtl8366rb_variant = {
 	.ds_ops_smi = &rtl8366rb_switch_ops_smi,
 	.ds_ops_mdio = &rtl8366rb_switch_ops_mdio,
 	.ops = &rtl8366rb_ops,
@@ -1921,7 +1921,7 @@  const struct realtek_variant rtl8366rb_variant = {
 	.cmd_write = 0xa8,
 	.chip_data_sz = sizeof(struct rtl8366rb),
 };
-EXPORT_SYMBOL_GPL(rtl8366rb_variant);
+module_realtek_variant(rtl8366rb_variant, "realtek,rtl8366rb");
 
 MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
 MODULE_DESCRIPTION("Driver for RTL8366RB ethernet switch");