diff mbox series

[net-next,10/16] net: pcs: xpcs: Add generic DW XPCS MDIO-device support

Message ID 20231205103559.9605-11-fancer.lancer@gmail.com
State New
Headers show
Series net: pcs: xpcs: Add memory-based management iface support | expand

Commit Message

Serge Semin Dec. 5, 2023, 10:35 a.m. UTC
Recently the memory mapped Synopsys DW XPCS management interface support
was added to the kernel. In that case the DW XPCS device can be registered
via a standard MDIO-bus subsystem. In order to have such devices fully
accessible and properly configured let's add a respective functionality to
the DW XPCS driver.

The main goal of this update is to add a functionality to activate
vendor-specific XPCS capabilities in the driver (like a limited number of
network interfaces, linkmodes, PMA-specific initializations). It's reached
by having DW XPCS devices registered as the platform devices (OF, ACPI,
legacy platform ,etc). From that point of view the suggested update is
threefold. First the driver is now capable to be attached to the
OF-devices registered on the MDIO-bus with the "snps,dw-xpcs*" compatible
string. Second it's possible to have the driver bound to the DW XPCS
device with no OF/ACPI-nodes by means of defining the mdio_board_info
descriptor and registering one with mdiobus_register_board_info() (see
dwmac-intel.c for example). Thirdly it's still possible to use the
unregistered device to auto-detect the DW XPCS device on the MDIO bus. In
all these cases the DW XPCS device info can be passed by means of the
driver-data pointer (of_device_id.data or device.platform_data).

In addition to that the update provides the DW XPCS reference clock
sources request and enabling. These clocks are named as "core" and "pad"
as per the DT-bindings. Note normally they are mutually exclusive: only
one of them can be used at a time, but the system software is responsible
for switching between them. Such functionality will be added later in the
framework of the pma_config() internal callback.

Note all the platform resources initialization is performed in the
externally called xpcs_create() method as before this update. The only
crucial update is that it now makes sure that the device is bound to the
DW XPCS driver if it's possible. Otherwise the legacy auto-detection
procedure takes place as before. Moreover due to that semantic there is no
device probe() and remove() methods defined since there is nothing left to
initialize/de-initialize on these stages.

Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
 drivers/net/pcs/Kconfig      |   6 +-
 drivers/net/pcs/pcs-xpcs.c   | 112 ++++++++++++++++++++++++++++++++---
 drivers/net/pcs/pcs-xpcs.h   |   6 ++
 include/linux/pcs/pcs-xpcs.h |  27 +++++++++
 4 files changed, 141 insertions(+), 10 deletions(-)

Comments

Vladimir Oltean Dec. 5, 2023, 11:13 a.m. UTC | #1
On Tue, Dec 05, 2023 at 01:35:31PM +0300, Serge Semin wrote:
> @@ -1436,21 +1480,32 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
>  	struct dw_xpcs *xpcs;
>  	int ret;
>  
> +	ret = device_attach(&mdiodev->dev);
> +	if (ret < 0 && ret != -ENODEV)
> +		return ERR_PTR(ret);
> +
>  	xpcs = xpcs_create_data(mdiodev);
>  	if (IS_ERR(xpcs))
>  		return xpcs;
>  
> +	ret = xpcs_init_clks(xpcs);
> +	if (ret)
> +		goto out_free_data;
> +
>  	ret = xpcs_init_id(xpcs);
>  	if (ret)
> -		goto out;
> +		goto out_clear_clks;
>  
>  	ret = xpcs_init_iface(xpcs, interface);
>  	if (ret)
> -		goto out;
> +		goto out_clear_clks;
>  
>  	return xpcs;

[    4.083518] Unable to handle kernel NULL pointer dereference at virtual address 00000000000000d0
[    4.092356] Mem abort info:
[    4.095164]   ESR = 0x0000000096000004
[    4.098932]   EC = 0x25: DABT (current EL), IL = 32 bits
[    4.104277]   SET = 0, FnV = 0
[    4.107352]   EA = 0, S1PTW = 0
[    4.110505]   FSC = 0x04: level 0 translation fault
[    4.115408] Data abort info:
[    4.118296]   ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
[    4.123807]   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[    4.128877]   GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[    4.134214] [00000000000000d0] user address but active_mm is swapper
[    4.140595] Internal error: Oops: 0000000096000004 [#1] PREEMPT SMP
[    4.146882] Modules linked in:
[    4.149944] CPU: 0 PID: 11 Comm: kworker/u4:0 Not tainted 6.7.0-rc3-00719-g75be5ea8e111-dirty #1551
[    4.164524] Workqueue: events_unbound deferred_probe_work_func
[    4.177372] pc : __device_attach+0x3c/0x1bc
[    4.181570] lr : __device_attach+0x38/0x1bc
[    4.185767] sp : ffff8000800f3800
[    4.189087] x29: ffff8000800f3820 x28: 0000000000000001 x27: ffff063781bda150
[    4.196252] x26: ffff063781bda150 x25: ffff063780827480 x24: ffffcb9a08138a40
[    4.203416] x23: ffff063781114080 x22: 0000000000000000 x21: 0000000000000004
[    4.210579] x20: ffff06378123a400 x19: ffff06378123a480 x18: ffffcb9a07c703a0
[    4.217743] x17: ffffcb9a07c703a4 x16: 00000000000000d4 x15: ffffcb9a07be70fc
[    4.224906] x14: ffffcb9a08299638 x13: 0000000000000053 x12: ffff003000000200
[    4.232069] x11: 0000000000000000 x10: 0000000000000000 x9 : 0000000000000000
[    4.239233] x8 : 0000000000000000 x7 : 0000000000000000 x6 : 000000000000003a
[    4.246396] x5 : ffff0637809a037b x4 : ffffcb9a087b0d47 x3 : ffff10300000020f
[    4.253560] x2 : ffffcb9a0910c561 x1 : 0000000000000000 x0 : ffff06378123a480
[    4.260724] Call trace:
[    4.263172]  __device_attach+0x3c/0x1bc
[    4.267020]  device_attach+0x14/0x20
[    4.270606]  xpcs_create+0x24/0x384
[    4.274107]  xpcs_create_byaddr+0x74/0xa0
[    4.278129]  sja1105_mdiobus_register+0xf8/0x478
[    4.282763]  sja1105_setup+0xb4/0x1194
[    4.286524]  dsa_register_switch+0xab0/0x11f8
[    4.290895]  sja1105_probe+0x2bc/0x2e4
[    4.294654]  spi_probe+0xa4/0xc4
[    4.297890]  really_probe+0x16c/0x3fc
[    4.301564]  __driver_probe_device+0xa4/0x168
[    4.305935]  driver_probe_device+0x3c/0x220
[    4.310131]  __device_attach_driver+0x128/0x1cc
[    4.314676]  bus_for_each_drv+0xf4/0x14c
[    4.318610]  __device_attach+0xfc/0x1bc
[    4.322457]  device_initial_probe+0x14/0x20
[    4.326654]  bus_probe_device+0x94/0x100
[    4.330587]  deferred_probe_work_func+0xa0/0xfc
[    4.335132]  process_scheduled_works+0x210/0x318
[    4.339764]  worker_thread+0x28c/0x450
[    4.343523]  kthread+0xfc/0x184
[    4.346669]  ret_from_fork+0x10/0x20
[    4.350256] Code: 2a0103f6 f81f83a8 9431ccd8 f9402688 (39434109)
[    4.356366] ---[ end trace 0000000000000000 ]---

I haven't looked at the code at all, but disassembling drivers/base/dd.lst,
I think the NPD is at dev->p->dead (0xa68 + 0x3c = 0xaa4).

0000000000000a68 <__device_attach>:
; {
     a68: d503233f     	paciasp
     a6c: d10143ff     	sub	sp, sp, #0x50
     a70: a9027bfd     	stp	x29, x30, [sp, #0x20]
     a74: a90357f6     	stp	x22, x21, [sp, #0x30]
     a78: a9044ff4     	stp	x20, x19, [sp, #0x40]
     a7c: 910083fd     	add	x29, sp, #0x20
     a80: d5384108     	mrs	x8, SP_EL0
; 	mutex_lock(&dev->mutex);
     a84: 91020013     	add	x19, x0, #0x80
     a88: f9423508     	ldr	x8, [x8, #0x468]
     a8c: aa0003f4     	mov	x20, x0
     a90: aa1303e0     	mov	x0, x19
     a94: 2a0103f6     	mov	w22, w1
     a98: f81f83a8     	stur	x8, [x29, #-0x8]
     a9c: 94000000     	bl	0xa9c <__device_attach+0x34>
		0000000000000a9c:  R_AARCH64_CALL26	mutex_lock
; 	if (dev->p->dead) {
     aa0: f9402688     	ldr	x8, [x20, #0x48]
     aa4: 39434109     	ldrb	w9, [x8, #0xd0]
     aa8: 37000129     	tbnz	w9, #0x0, 0xacc <__device_attach+0x64>
; 	} else if (dev->driver) {
     aac: f9403689     	ldr	x9, [x20, #0x68]
     ab0: b40003e9     	cbz	x9, 0xb2c <__device_attach+0xc4>
; 	return dev->p && klist_node_attached(&dev->p->knode_driver);
     ab4: b40002a8     	cbz	x8, 0xb08 <__device_attach+0xa0>
     ab8: 91012100     	add	x0, x8, #0x48
     abc: 94000000     	bl	0xabc <__device_attach+0x54>
		0000000000000abc:  R_AARCH64_CALL26	klist_node_attached
; 		if (device_is_bound(dev)) {
     ac0: 34000240     	cbz	w0, 0xb08 <__device_attach+0xa0>
     ac4: 52800035     	mov	w21, #0x1
     ac8: 14000002     	b	0xad0 <__device_attach+0x68>
     acc: 2a1f03f5     	mov	w21, wzr
; 	mutex_unlock(&dev->mutex);
     ad0: aa1303e0     	mov	x0, x19
     ad4: 94000000     	bl	0xad4 <__device_attach+0x6c>
		0000000000000ad4:  R_AARCH64_CALL26	mutex_unlock
     ad8: d5384108     	mrs	x8, SP_EL0
     adc: f9423508     	ldr	x8, [x8, #0x468]
     ae0: f85f83a9     	ldur	x9, [x29, #-0x8]
     ae4: eb09011f     	cmp	x8, x9
     ae8: 540008c1     	b.ne	0xc00 <__device_attach+0x198>
; 	return ret;
     aec: 2a1503e0     	mov	w0, w21
     af0: a9444ff4     	ldp	x20, x19, [sp, #0x40]
     af4: a94357f6     	ldp	x22, x21, [sp, #0x30]
     af8: a9427bfd     	ldp	x29, x30, [sp, #0x20]
     afc: 910143ff     	add	sp, sp, #0x50
     b00: d50323bf     	autiasp
     b04: d65f03c0     	ret
; 	ret = driver_sysfs_add(dev);
     b08: aa1403e0     	mov	x0, x20
     b0c: 97ffff21     	bl	0x790 <driver_sysfs_add>
; 	if (!ret) {
     b10: 340006c0     	cbz	w0, 0xbe8 <__device_attach+0x180>
; 		bus_notify(dev, BUS_NOTIFY_DRIVER_NOT_BOUND);
     b14: aa1403e0     	mov	x0, x20
     b18: 528000e1     	mov	w1, #0x7
     b1c: 94000000     	bl	0xb1c <__device_attach+0xb4>
		0000000000000b1c:  R_AARCH64_CALL26	bus_notify
     b20: 2a1f03f5     	mov	w21, wzr
; 			dev->driver = NULL;
     b24: f900369f     	str	xzr, [x20, #0x68]
     b28: 17ffffea     	b	0xad0 <__device_attach+0x68>
     b2c: 120002c8     	and	w8, w22, #0x1
; 		if (dev->parent)
     b30: f9402280     	ldr	x0, [x20, #0x40]
; 		struct device_attach_data data = {
     b34: a900fff4     	stp	x20, xzr, [sp, #0x8]
     b38: 39004bff     	strb	wzr, [sp, #0x12]
     b3c: 390043e8     	strb	w8, [sp, #0x10]
; 		if (dev->parent)
     b40: b4000060     	cbz	x0, 0xb4c <__device_attach+0xe4>
; 	return __pm_runtime_resume(dev, RPM_GET_PUT);
     b44: 52800081     	mov	w1, #0x4
     b48: 94000000     	bl	0xb48 <__device_attach+0xe0>
		0000000000000b48:  R_AARCH64_CALL26	__pm_runtime_resume
; 		ret = bus_for_each_drv(dev->bus, NULL, &data,
     b4c: f9403280     	ldr	x0, [x20, #0x60]
     b50: 90000003     	adrp	x3, 0x0 <driver_deferred_probe_add>
		0000000000000b50:  R_AARCH64_ADR_PREL_PG_HI21	.text+0x17ac
     b54: 91000063     	add	x3, x3, #0x0
		0000000000000b54:  R_AARCH64_ADD_ABS_LO12_NC	.text+0x17ac
     b58: 910023e2     	add	x2, sp, #0x8
     b5c: aa1f03e1     	mov	x1, xzr
     b60: 94000000     	bl	0xb60 <__device_attach+0xf8>
		0000000000000b60:  R_AARCH64_CALL26	bus_for_each_drv
     b64: 39404be8     	ldrb	w8, [sp, #0x12]
; 		if (!ret && allow_async && data.have_async) {
     b68: 7100001f     	cmp	w0, #0x0
     b6c: 1a9f07e9     	cset	w9, ne
; 		ret = bus_for_each_drv(dev->bus, NULL, &data,
     b70: 2a0003f5     	mov	w21, w0
     b74: 7100011f     	cmp	w8, #0x0
; 		if (!ret && allow_async && data.have_async) {
     b78: 520002c8     	eor	w8, w22, #0x1
     b7c: 1a9f17ea     	cset	w10, eq
     b80: 2a0a0108     	orr	w8, w8, w10
     b84: 2a080136     	orr	w22, w9, w8
     b88: 360000f6     	tbz	w22, #0x0, 0xba4 <__device_attach+0x13c>
; 	return __pm_runtime_idle(dev, RPM_ASYNC);
     b8c: aa1403e0     	mov	x0, x20
     b90: 52800021     	mov	w1, #0x1
     b94: 94000000     	bl	0xb94 <__device_attach+0x12c>
		0000000000000b94:  R_AARCH64_CALL26	__pm_runtime_idle
; 		if (dev->parent)
     b98: f9402280     	ldr	x0, [x20, #0x40]
     b9c: b50000e0     	cbnz	x0, 0xbb8 <__device_attach+0x150>
     ba0: 14000008     	b	0xbc0 <__device_attach+0x158>
; 	asm_volatile_goto(
     ba4: d503201f     	nop
Serge Semin Dec. 5, 2023, 11:35 a.m. UTC | #2
Hi Vladimir

On Tue, Dec 05, 2023 at 01:13:51PM +0200, Vladimir Oltean wrote:
> On Tue, Dec 05, 2023 at 01:35:31PM +0300, Serge Semin wrote:
> > @@ -1436,21 +1480,32 @@ static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
> >  	struct dw_xpcs *xpcs;
> >  	int ret;
> >  
> > +	ret = device_attach(&mdiodev->dev);
> > +	if (ret < 0 && ret != -ENODEV)
> > +		return ERR_PTR(ret);
> > +
> >  	xpcs = xpcs_create_data(mdiodev);
> >  	if (IS_ERR(xpcs))
> >  		return xpcs;
> >  
> > +	ret = xpcs_init_clks(xpcs);
> > +	if (ret)
> > +		goto out_free_data;
> > +
> >  	ret = xpcs_init_id(xpcs);
> >  	if (ret)
> > -		goto out;
> > +		goto out_clear_clks;
> >  
> >  	ret = xpcs_init_iface(xpcs, interface);
> >  	if (ret)
> > -		goto out;
> > +		goto out_clear_clks;
> >  
> >  	return xpcs;
> 
> [    4.083518] Unable to handle kernel NULL pointer dereference at virtual address 00000000000000d0
> [    4.092356] Mem abort info:
> [    4.095164]   ESR = 0x0000000096000004
> [    4.098932]   EC = 0x25: DABT (current EL), IL = 32 bits
> [    4.104277]   SET = 0, FnV = 0
> [    4.107352]   EA = 0, S1PTW = 0
> [    4.110505]   FSC = 0x04: level 0 translation fault
> [    4.115408] Data abort info:
> [    4.118296]   ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
> [    4.123807]   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
> [    4.128877]   GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
> [    4.134214] [00000000000000d0] user address but active_mm is swapper
> [    4.140595] Internal error: Oops: 0000000096000004 [#1] PREEMPT SMP
> [    4.146882] Modules linked in:
> [    4.149944] CPU: 0 PID: 11 Comm: kworker/u4:0 Not tainted 6.7.0-rc3-00719-g75be5ea8e111-dirty #1551
> [    4.164524] Workqueue: events_unbound deferred_probe_work_func
> [    4.177372] pc : __device_attach+0x3c/0x1bc
> [    4.181570] lr : __device_attach+0x38/0x1bc
> [    4.185767] sp : ffff8000800f3800
> [    4.189087] x29: ffff8000800f3820 x28: 0000000000000001 x27: ffff063781bda150
> [    4.196252] x26: ffff063781bda150 x25: ffff063780827480 x24: ffffcb9a08138a40
> [    4.203416] x23: ffff063781114080 x22: 0000000000000000 x21: 0000000000000004
> [    4.210579] x20: ffff06378123a400 x19: ffff06378123a480 x18: ffffcb9a07c703a0
> [    4.217743] x17: ffffcb9a07c703a4 x16: 00000000000000d4 x15: ffffcb9a07be70fc
> [    4.224906] x14: ffffcb9a08299638 x13: 0000000000000053 x12: ffff003000000200
> [    4.232069] x11: 0000000000000000 x10: 0000000000000000 x9 : 0000000000000000
> [    4.239233] x8 : 0000000000000000 x7 : 0000000000000000 x6 : 000000000000003a
> [    4.246396] x5 : ffff0637809a037b x4 : ffffcb9a087b0d47 x3 : ffff10300000020f
> [    4.253560] x2 : ffffcb9a0910c561 x1 : 0000000000000000 x0 : ffff06378123a480
> [    4.260724] Call trace:
> [    4.263172]  __device_attach+0x3c/0x1bc
> [    4.267020]  device_attach+0x14/0x20
> [    4.270606]  xpcs_create+0x24/0x384
> [    4.274107]  xpcs_create_byaddr+0x74/0xa0
> [    4.278129]  sja1105_mdiobus_register+0xf8/0x478
> [    4.282763]  sja1105_setup+0xb4/0x1194
> [    4.286524]  dsa_register_switch+0xab0/0x11f8
> [    4.290895]  sja1105_probe+0x2bc/0x2e4
> [    4.294654]  spi_probe+0xa4/0xc4
> [    4.297890]  really_probe+0x16c/0x3fc
> [    4.301564]  __driver_probe_device+0xa4/0x168
> [    4.305935]  driver_probe_device+0x3c/0x220
> [    4.310131]  __device_attach_driver+0x128/0x1cc
> [    4.314676]  bus_for_each_drv+0xf4/0x14c
> [    4.318610]  __device_attach+0xfc/0x1bc
> [    4.322457]  device_initial_probe+0x14/0x20
> [    4.326654]  bus_probe_device+0x94/0x100
> [    4.330587]  deferred_probe_work_func+0xa0/0xfc
> [    4.335132]  process_scheduled_works+0x210/0x318
> [    4.339764]  worker_thread+0x28c/0x450
> [    4.343523]  kthread+0xfc/0x184
> [    4.346669]  ret_from_fork+0x10/0x20
> [    4.350256] Code: 2a0103f6 f81f83a8 9431ccd8 f9402688 (39434109)
> [    4.356366] ---[ end trace 0000000000000000 ]---
> 
> I haven't looked at the code at all, but disassembling drivers/base/dd.lst,
> I think the NPD is at dev->p->dead (0xa68 + 0x3c = 0xaa4).
> 
> 0000000000000a68 <__device_attach>:
> ; {
>      a68: d503233f     	paciasp
>      a6c: d10143ff     	sub	sp, sp, #0x50
>      a70: a9027bfd     	stp	x29, x30, [sp, #0x20]
>      a74: a90357f6     	stp	x22, x21, [sp, #0x30]
>      a78: a9044ff4     	stp	x20, x19, [sp, #0x40]
>      a7c: 910083fd     	add	x29, sp, #0x20
>      a80: d5384108     	mrs	x8, SP_EL0
> ; 	mutex_lock(&dev->mutex);
>      a84: 91020013     	add	x19, x0, #0x80
>      a88: f9423508     	ldr	x8, [x8, #0x468]
>      a8c: aa0003f4     	mov	x20, x0
>      a90: aa1303e0     	mov	x0, x19
>      a94: 2a0103f6     	mov	w22, w1
>      a98: f81f83a8     	stur	x8, [x29, #-0x8]
>      a9c: 94000000     	bl	0xa9c <__device_attach+0x34>
> 		0000000000000a9c:  R_AARCH64_CALL26	mutex_lock
> ; 	if (dev->p->dead) {
>      aa0: f9402688     	ldr	x8, [x20, #0x48]
>      aa4: 39434109     	ldrb	w9, [x8, #0xd0]
>      aa8: 37000129     	tbnz	w9, #0x0, 0xacc <__device_attach+0x64>
> ; 	} else if (dev->driver) {
>      aac: f9403689     	ldr	x9, [x20, #0x68]
>      ab0: b40003e9     	cbz	x9, 0xb2c <__device_attach+0xc4>
> ; 	return dev->p && klist_node_attached(&dev->p->knode_driver);
>      ab4: b40002a8     	cbz	x8, 0xb08 <__device_attach+0xa0>
>      ab8: 91012100     	add	x0, x8, #0x48
>      abc: 94000000     	bl	0xabc <__device_attach+0x54>
> 		0000000000000abc:  R_AARCH64_CALL26	klist_node_attached
> ; 		if (device_is_bound(dev)) {
>      ac0: 34000240     	cbz	w0, 0xb08 <__device_attach+0xa0>
>      ac4: 52800035     	mov	w21, #0x1
>      ac8: 14000002     	b	0xad0 <__device_attach+0x68>
>      acc: 2a1f03f5     	mov	w21, wzr
> ; 	mutex_unlock(&dev->mutex);
>      ad0: aa1303e0     	mov	x0, x19
>      ad4: 94000000     	bl	0xad4 <__device_attach+0x6c>
> 		0000000000000ad4:  R_AARCH64_CALL26	mutex_unlock
>      ad8: d5384108     	mrs	x8, SP_EL0
>      adc: f9423508     	ldr	x8, [x8, #0x468]
>      ae0: f85f83a9     	ldur	x9, [x29, #-0x8]
>      ae4: eb09011f     	cmp	x8, x9
>      ae8: 540008c1     	b.ne	0xc00 <__device_attach+0x198>
> ; 	return ret;
>      aec: 2a1503e0     	mov	w0, w21
>      af0: a9444ff4     	ldp	x20, x19, [sp, #0x40]
>      af4: a94357f6     	ldp	x22, x21, [sp, #0x30]
>      af8: a9427bfd     	ldp	x29, x30, [sp, #0x20]
>      afc: 910143ff     	add	sp, sp, #0x50
>      b00: d50323bf     	autiasp
>      b04: d65f03c0     	ret
> ; 	ret = driver_sysfs_add(dev);
>      b08: aa1403e0     	mov	x0, x20
>      b0c: 97ffff21     	bl	0x790 <driver_sysfs_add>
> ; 	if (!ret) {
>      b10: 340006c0     	cbz	w0, 0xbe8 <__device_attach+0x180>
> ; 		bus_notify(dev, BUS_NOTIFY_DRIVER_NOT_BOUND);
>      b14: aa1403e0     	mov	x0, x20
>      b18: 528000e1     	mov	w1, #0x7
>      b1c: 94000000     	bl	0xb1c <__device_attach+0xb4>
> 		0000000000000b1c:  R_AARCH64_CALL26	bus_notify
>      b20: 2a1f03f5     	mov	w21, wzr
> ; 			dev->driver = NULL;
>      b24: f900369f     	str	xzr, [x20, #0x68]
>      b28: 17ffffea     	b	0xad0 <__device_attach+0x68>
>      b2c: 120002c8     	and	w8, w22, #0x1
> ; 		if (dev->parent)
>      b30: f9402280     	ldr	x0, [x20, #0x40]
> ; 		struct device_attach_data data = {
>      b34: a900fff4     	stp	x20, xzr, [sp, #0x8]
>      b38: 39004bff     	strb	wzr, [sp, #0x12]
>      b3c: 390043e8     	strb	w8, [sp, #0x10]
> ; 		if (dev->parent)
>      b40: b4000060     	cbz	x0, 0xb4c <__device_attach+0xe4>
> ; 	return __pm_runtime_resume(dev, RPM_GET_PUT);
>      b44: 52800081     	mov	w1, #0x4
>      b48: 94000000     	bl	0xb48 <__device_attach+0xe0>
> 		0000000000000b48:  R_AARCH64_CALL26	__pm_runtime_resume
> ; 		ret = bus_for_each_drv(dev->bus, NULL, &data,
>      b4c: f9403280     	ldr	x0, [x20, #0x60]
>      b50: 90000003     	adrp	x3, 0x0 <driver_deferred_probe_add>
> 		0000000000000b50:  R_AARCH64_ADR_PREL_PG_HI21	.text+0x17ac
>      b54: 91000063     	add	x3, x3, #0x0
> 		0000000000000b54:  R_AARCH64_ADD_ABS_LO12_NC	.text+0x17ac
>      b58: 910023e2     	add	x2, sp, #0x8
>      b5c: aa1f03e1     	mov	x1, xzr
>      b60: 94000000     	bl	0xb60 <__device_attach+0xf8>
> 		0000000000000b60:  R_AARCH64_CALL26	bus_for_each_drv
>      b64: 39404be8     	ldrb	w8, [sp, #0x12]
> ; 		if (!ret && allow_async && data.have_async) {
>      b68: 7100001f     	cmp	w0, #0x0
>      b6c: 1a9f07e9     	cset	w9, ne
> ; 		ret = bus_for_each_drv(dev->bus, NULL, &data,
>      b70: 2a0003f5     	mov	w21, w0
>      b74: 7100011f     	cmp	w8, #0x0
> ; 		if (!ret && allow_async && data.have_async) {
>      b78: 520002c8     	eor	w8, w22, #0x1
>      b7c: 1a9f17ea     	cset	w10, eq
>      b80: 2a0a0108     	orr	w8, w8, w10
>      b84: 2a080136     	orr	w22, w9, w8
>      b88: 360000f6     	tbz	w22, #0x0, 0xba4 <__device_attach+0x13c>
> ; 	return __pm_runtime_idle(dev, RPM_ASYNC);
>      b8c: aa1403e0     	mov	x0, x20
>      b90: 52800021     	mov	w1, #0x1
>      b94: 94000000     	bl	0xb94 <__device_attach+0x12c>
> 		0000000000000b94:  R_AARCH64_CALL26	__pm_runtime_idle
> ; 		if (dev->parent)
>      b98: f9402280     	ldr	x0, [x20, #0x40]
>      b9c: b50000e0     	cbnz	x0, 0xbb8 <__device_attach+0x150>
>      ba0: 14000008     	b	0xbc0 <__device_attach+0x158>
> ; 	asm_volatile_goto(
>      ba4: d503201f     	nop

Omg, thank you very much for testing the series straight away and
sorry for the immediate trouble it caused. I'll need some more time
for investigation. I'll get back to this topic a bit later on this
week.

-Serge(y)
Vladimir Oltean Dec. 5, 2023, 12:23 p.m. UTC | #3
On Tue, Dec 05, 2023 at 02:35:46PM +0300, Serge Semin wrote:
> Omg, thank you very much for testing the series straight away and
> sorry for the immediate trouble it caused. I'll need some more time
> for investigation. I'll get back to this topic a bit later on this
> week.

Don't worry, I got suspicious when I was CCed to review only a one-line
change in patch 11/16. It's never about that one line, is it?)

Anyway, the NULL dev->p is a symptom of device_add() not having been
called, most likely from mdio_device_register().

I'll be honest and say that I still don't quite understand what you're
trying to achieve. You're trying to bind the hardcoded mdio_devices
created by xpcs_create() to a driver? That was attempted a while ago by
Sean Anderson with the Lynx PCS. Are you aware of the fact that even in
the good case in which binding the driver actually works, the user can
then come along and unbind it from the PCS device, and phylink isn't
prepared to handle that, so it will crash the kernel upon the next
phylink_pcs call?

The pcs-rzn1-miic.c driver puts a device_link to the MAC to at least
tear down the whole thing when the PCS is unbound, which is saner than
crashing the kernel. I don't see the equivalent protection mechanism here?

Can't the xpcs continue to live without a bound driver? Having a
compatible string in the OF description is perfectly fine though,
and should absolutely not preclude that.
Serge Semin Dec. 8, 2023, 2:11 p.m. UTC | #4
Hi Vladimir

On Tue, Dec 05, 2023 at 02:23:16PM +0200, Vladimir Oltean wrote:
> On Tue, Dec 05, 2023 at 02:35:46PM +0300, Serge Semin wrote:
> > Omg, thank you very much for testing the series straight away and
> > sorry for the immediate trouble it caused. I'll need some more time
> > for investigation. I'll get back to this topic a bit later on this
> > week.
> 
> Don't worry, I got suspicious when I was CCed to review only a one-line
> change in patch 11/16. It's never about that one line, is it?)

Right. I should have added you to the list of recipients of the entire
series since the patchset changes more than that. The bug you caught
brightly highlights my mistake. I'll make sure you are in the list.
I'll add Jiawen and Mengyuan there too since the driver under their
maintenance is also affected. Hopefully they'll get to test the series
too.

> 
> Anyway, the NULL dev->p is a symptom of device_add() not having been
> called, most likely from mdio_device_register().

Absolutely right. I thought that mdio_device_create() having the
device_initialize() method called was enough for the device_attach()
function being happy. It turns out it wasn't and I missed that the
device_private instance is allocated only on the device registration.
So I'll need to call mdio_device_register() after all, if I get to
preserve the current design of the solution.

> 
> I'll be honest and say that I still don't quite understand what you're
> trying to achieve. You're trying to bind the hardcoded mdio_devices
> created by xpcs_create() to a driver? 

My idea was to reuse the mdio_device which has already been created
either by means of the MDIO-bus OF-subnode or by means of the MDIO-bus
board_info infrastructure (can be utilized in the SJA1105 or Wangxun
Tx GBE). The xpcs_create() method then either probes the device on the MDIO
bus and gets ID from there, or just uses the custom IDs based on the
OF compatible match table or on the platform_data. If no MDIO-device
was created my patchset is supposed to preserve the previous
semantics: create MDIO-device, probe the device on the MDIO-bus, get
device IDs from there. See the next patch for more details:
https://lore.kernel.org/netdev/20231205103559.9605-11-fancer.lancer@gmail.com/

> That was attempted a while ago by
> Sean Anderson with the Lynx PCS. Are you aware of the fact that even in
> the good case in which binding the driver actually works, the user can
> then come along and unbind it from the PCS device, and phylink isn't
> prepared to handle that, so it will crash the kernel upon the next
> phylink_pcs call?

To be honest I didn't consider the driver bind/unbind option. But my
case a bit different. DW XPCS MDIO-device is supposed to be created
automatically by means of the DW XPCS MI driver from the DT-nodes
hierarchy like this:
mdio@1f05d000 {
	compatible = "snps,dw-xpcs-mi";
	reg = <0 0x1f05d000 0 0x1000>;

	xgmac_pcs: ethernet-pcs@0 {
		compatible = "snps,dw-xpcs";
		reg = <0>;
	};
};
The platform-device is created for the mdio@1f05d000 node for which
the DW XPCS MI driver is loaded, which calls the
devm_of_mdiobus_register() in the probe() method which registers the
MDIO-bus and then creates the MDIO-device from the ethernet-pcs@0
node. The DW XPCS MDIO-device driver is attached to that MDIO-device
then. In such model the PCS can be supplied to the DW *MAC via the
"pcs-handle = &xgmac_pcs" property.

Regarding the current semantics it's preserved in the framework of the
xpcs_create_byaddr() method (former xpcs_create_mdiodev()) by means of
the next code snippet:
        if (mdiobus_is_registered_device(bus, addr)) {
                mdiodev = bus->mdio_map[addr];
                mdio_device_get(mdiodev);
        } else {
                mdiodev = mdio_device_create(bus, addr);
                if (IS_ERR(mdiodev))
                        return ERR_CAST(mdiodev);
        }
Device can be automatically created if before registering the MDIO-bus
the xpcs_create_byaddr() caller registered the MDIO-device board info
by means of the mdiobus_register_board_info() method. In addition to
that it's now possible to supply some custom data (custom device IDs
in my implementation) to the XPCS driver by means of the
mdio_board_info.platform_data field. See the next patch for
reference:
https://lore.kernel.org/netdev/20231205103559.9605-14-fancer.lancer@gmail.com

So what the difference with the Lynx PCS is that in my case the
MDIO-device is created automatically as a result of the DW XPCS MI
MDIO-bus registration. Additionally I implemented the MDIO-device
creation based on the MDIO-board-info, thus there won't be need in the
calling mdio_device_create() on each xpcs_create_mdiodev() invocation.
The later part isn't that important in the framework of this
conversation, but just so you be aware.

Regarding the driver bind/unbind. As I said I didn't actually consider
that option. On the other hand my DW XPCS MDIO-device driver doesn't
do actual probe() or remove(). The only implemented thing is the
of_device_id table, which is used to assign PCS and PMA IDs if
required based on the DT compatible property. So I can easily drop any
MDIO device-driver part and parse the of_device_id table right in the
xpcs_create_bynode(). From that perspective my implementation won't
differ much from the Lynx PCS design. The only difference will be is
the way the MDIO-bus is created and registered. In case of Lynx PCS
the bus is created by the MAC-driver itself. In my case DW XPCS MI is
currently created in the framework of the separate platform driver. Do
you think it would be better to follow the Lynx design pattern in
order to get rid from the possibility of the DW XPCS MI driver being
unbound behind the STMMAC+XPCS couple back?

In this case the Dw MAC DT-node hierarchy would look like this:

xgmac: ethernet@1f054000 {
	compatible = "snps,dwxgmac";
	reg = <0 0x1f054000 0 0x4000>;
	reg-names = "stmmaceth";
	ranges;

	...

	pcs-handle = &xgmac_pcs;

	// DW XPCS MI to access the DW XPCS attached to the device
	mdio@1f05d000 {
		compatible = "snps,dwmac-mi";
		reg = <0 0x1f05d000 0 0x1000>;

		xgmac_pcs: ethernet-pcs@0 {
			compatible = "snps,dw-xpcs";
			reg = <0>;
		};
	};

	// Normal MDIO-bus to access external PHYs (it's also called
	// as SMA - Station Management Agent - by Synopsys)
	mdio {
		compatible = "snps,dwmac-mdio";
		#address-cells = <1>;
		#size-cells = <0>;
	};
};

I actually thought to use that hardware description pattern instead,
but after some meditation around that I decided that having the DW
XPCS device defined separately from the DW MAC node seemed better at
least from the code separation point of view. Now I think that it
wasn't the best decision. DW XPCS is always attached to the DW XGMAC
controller. So it would be more correct having it defined as a
sub-node. It would also helped to avoid the platform device driver
bind/unbind problem.

What do you think? Should I re-design my patchset to be supporting the
design above? (After having conversion with you I am more inclined to
do that now than to stick with the currently implemented solution.)

> 
> The pcs-rzn1-miic.c driver puts a device_link to the MAC to at least
> tear down the whole thing when the PCS is unbound, which is saner than
> crashing the kernel. I don't see the equivalent protection mechanism here?

You are right. I don't have any equivalent protection here. Thanks for
suggesting a solution.

> 
> Can't the xpcs continue to live without a bound driver? Having a
> compatible string in the OF description is perfectly fine though,
> and should absolutely not preclude that.

As I explained above Dw XPCS device can live without a bound driver
because the DW XPCS MDIO-driver doesn't do much but merely gets to be
bound based on the of_device_id table. In my case the problem is in
the DW XPCS MI driver which indeed can be detached. Please see my
long-read text above.

-Serge(y)
Vladimir Oltean Dec. 8, 2023, 4:33 p.m. UTC | #5
On Fri, Dec 08, 2023 at 05:11:20PM +0300, Serge Semin wrote:
> My idea was to reuse the mdio_device which has already been created
> either by means of the MDIO-bus OF-subnode or by means of the MDIO-bus
> board_info infrastructure (can be utilized in the SJA1105 or Wangxun
> Tx GBE). The xpcs_create() method then either probes the device on the MDIO
> bus and gets ID from there, or just uses the custom IDs based on the
> OF compatible match table or on the platform_data. If no MDIO-device
> was created my patchset is supposed to preserve the previous
> semantics: create MDIO-device, probe the device on the MDIO-bus, get
> device IDs from there. See the next patch for more details:
> https://lore.kernel.org/netdev/20231205103559.9605-11-fancer.lancer@gmail.com/
> 
> > That was attempted a while ago by
> > Sean Anderson with the Lynx PCS. Are you aware of the fact that even in
> > the good case in which binding the driver actually works, the user can
> > then come along and unbind it from the PCS device, and phylink isn't
> > prepared to handle that, so it will crash the kernel upon the next
> > phylink_pcs call?
> 
> To be honest I didn't consider the driver bind/unbind option. But my
> case a bit different. DW XPCS MDIO-device is supposed to be created
> automatically by means of the DW XPCS MI driver from the DT-nodes
> hierarchy like this:
> mdio@1f05d000 {
> 	compatible = "snps,dw-xpcs-mi";
> 	reg = <0 0x1f05d000 0 0x1000>;
> 
> 	xgmac_pcs: ethernet-pcs@0 {
> 		compatible = "snps,dw-xpcs";
> 		reg = <0>;
> 	};
> };
> The platform-device is created for the mdio@1f05d000 node for which
> the DW XPCS MI driver is loaded, which calls the
> devm_of_mdiobus_register() in the probe() method which registers the
> MDIO-bus and then creates the MDIO-device from the ethernet-pcs@0
> node. The DW XPCS MDIO-device driver is attached to that MDIO-device
> then. In such model the PCS can be supplied to the DW *MAC via the
> "pcs-handle = &xgmac_pcs" property.
> 
> Regarding the current semantics it's preserved in the framework of the
> xpcs_create_byaddr() method (former xpcs_create_mdiodev()) by means of
> the next code snippet:
>         if (mdiobus_is_registered_device(bus, addr)) {
>                 mdiodev = bus->mdio_map[addr];
>                 mdio_device_get(mdiodev);
>         } else {
>                 mdiodev = mdio_device_create(bus, addr);
>                 if (IS_ERR(mdiodev))
>                         return ERR_CAST(mdiodev);
>         }
> Device can be automatically created if before registering the MDIO-bus
> the xpcs_create_byaddr() caller registered the MDIO-device board info
> by means of the mdiobus_register_board_info() method. In addition to
> that it's now possible to supply some custom data (custom device IDs
> in my implementation) to the XPCS driver by means of the
> mdio_board_info.platform_data field. See the next patch for
> reference:
> https://lore.kernel.org/netdev/20231205103559.9605-14-fancer.lancer@gmail.com
> 
> So what the difference with the Lynx PCS is that in my case the
> MDIO-device is created automatically as a result of the DW XPCS MI
> MDIO-bus registration. Additionally I implemented the MDIO-device
> creation based on the MDIO-board-info, thus there won't be need in the
> calling mdio_device_create() on each xpcs_create_mdiodev() invocation.
> The later part isn't that important in the framework of this
> conversation, but just so you be aware.

It's not really different, though. You can connect to the Lynx PCS both
ways, see dpaa2_pcs_create() which also searches for a pcs-handle before
calling lynx_pcs_create_fwnode(). What's subtly different is that we
don't (yet) have "fsl,lynx-pcs" compatible strings in the device tree.
So the MDIO controller will register the PCS devices as struct phy_device
(which still have an underlying struct mdio_device). The PCS layer
connects to the underlying struct mdio_device, and the phy_device on top
remains unconnected to any phylib/phylink MAC driver. That is confusing,
I should really get to adding those compatible strings to suppress the
phy_device creation.

> Regarding the driver bind/unbind. As I said I didn't actually consider
> that option. On the other hand my DW XPCS MDIO-device driver doesn't
> do actual probe() or remove(). The only implemented thing is the
> of_device_id table, which is used to assign PCS and PMA IDs if
> required based on the DT compatible property. So I can easily drop any
> MDIO device-driver part and parse the of_device_id table right in the
> xpcs_create_bynode(). From that perspective my implementation won't
> differ much from the Lynx PCS design. The only difference will be is
> the way the MDIO-bus is created and registered. In case of Lynx PCS
> the bus is created by the MAC-driver itself.

Nope, not true. Follow the pcs-handle in arch/arm64/boot/dts/freescale/fsl-lx2160a.dtsi.

> In my case DW XPCS MI is currently created in the framework of the
> separate platform driver. Do you think it would be better to follow
> the Lynx design pattern in order to get rid from the possibility of
> the DW XPCS MI driver being unbound behind the STMMAC+XPCS couple
> back?

I think you actually pointed out a flaw in the Lynx PCS design too.
Actually, it is a larger flaw in the kernel. You can also unbind the
MDIO bus which holds the phy_device, and phylib (and therefore also
phylink) won't expect that either, so it will crash.

> In this case the Dw MAC DT-node hierarchy would look like this:
> 
> xgmac: ethernet@1f054000 {
> 	compatible = "snps,dwxgmac";
> 	reg = <0 0x1f054000 0 0x4000>;
> 	reg-names = "stmmaceth";
> 	ranges;
> 
> 	...
> 
> 	pcs-handle = &xgmac_pcs;
> 
> 	// DW XPCS MI to access the DW XPCS attached to the device
> 	mdio@1f05d000 {
> 		compatible = "snps,dwmac-mi";
> 		reg = <0 0x1f05d000 0 0x1000>;
> 
> 		xgmac_pcs: ethernet-pcs@0 {
> 			compatible = "snps,dw-xpcs";
> 			reg = <0>;
> 		};
> 	};
> 
> 	// Normal MDIO-bus to access external PHYs (it's also called
> 	// as SMA - Station Management Agent - by Synopsys)
> 	mdio {
> 		compatible = "snps,dwmac-mdio";
> 		#address-cells = <1>;
> 		#size-cells = <0>;
> 	};
> };
> 
> I actually thought to use that hardware description pattern instead,
> but after some meditation around that I decided that having the DW
> XPCS device defined separately from the DW MAC node seemed better at
> least from the code separation point of view. Now I think that it
> wasn't the best decision. DW XPCS is always attached to the DW XGMAC
> controller. So it would be more correct having it defined as a
> sub-node. It would also helped to avoid the platform device driver
> bind/unbind problem.
> 
> What do you think? Should I re-design my patchset to be supporting the
> design above? (After having conversion with you I am more inclined to
> do that now than to stick with the currently implemented solution.)

I think that the placement of the "mdio" node as lateral vs subordinate
to the "ethernet" node would have fixed the issue by mistake. We should
be looking at it as a structural problem of the kernel instead. Don't
let it influence what you believe should be the correct design.

> > The pcs-rzn1-miic.c driver puts a device_link to the MAC to at least
> > tear down the whole thing when the PCS is unbound, which is saner than
> > crashing the kernel. I don't see the equivalent protection mechanism here?
> 
> You are right. I don't have any equivalent protection here. Thanks for
> suggesting a solution.

I think that a device link between the "ethernet" device and the "mdio"
device (controller, parent of the PHY or PCS), if the Ethernet is not a
parent of the MDIO controller, could also solve that. But it would also
require ACK from PHY maintainers, who may have grander plans to address
this snag.

> > Can't the xpcs continue to live without a bound driver? Having a
> > compatible string in the OF description is perfectly fine though,
> > and should absolutely not preclude that.
> 
> As I explained above Dw XPCS device can live without a bound driver
> because the DW XPCS MDIO-driver doesn't do much but merely gets to be
> bound based on the of_device_id table. In my case the problem is in
> the DW XPCS MI driver which indeed can be detached. Please see my
> long-read text above.

Yeah, common design, common problem.
Serge Semin Dec. 14, 2023, 11:54 a.m. UTC | #6
Hi Vladimir,

Sorry for the delayed response. Too much work at this time of the year
(ah, Decembers)..(

On Fri, Dec 08, 2023 at 06:33:43PM +0200, Vladimir Oltean wrote:
> On Fri, Dec 08, 2023 at 05:11:20PM +0300, Serge Semin wrote:
> > My idea was to reuse the mdio_device which has already been created
> > either by means of the MDIO-bus OF-subnode or by means of the MDIO-bus
> > board_info infrastructure (can be utilized in the SJA1105 or Wangxun
> > Tx GBE). The xpcs_create() method then either probes the device on the MDIO
> > bus and gets ID from there, or just uses the custom IDs based on the
> > OF compatible match table or on the platform_data. If no MDIO-device
> > was created my patchset is supposed to preserve the previous
> > semantics: create MDIO-device, probe the device on the MDIO-bus, get
> > device IDs from there. See the next patch for more details:
> > https://lore.kernel.org/netdev/20231205103559.9605-11-fancer.lancer@gmail.com/
> > 
> > > That was attempted a while ago by
> > > Sean Anderson with the Lynx PCS. Are you aware of the fact that even in
> > > the good case in which binding the driver actually works, the user can
> > > then come along and unbind it from the PCS device, and phylink isn't
> > > prepared to handle that, so it will crash the kernel upon the next
> > > phylink_pcs call?
> > 
> > To be honest I didn't consider the driver bind/unbind option. But my
> > case a bit different. DW XPCS MDIO-device is supposed to be created
> > automatically by means of the DW XPCS MI driver from the DT-nodes
> > hierarchy like this:
> > mdio@1f05d000 {
> > 	compatible = "snps,dw-xpcs-mi";
> > 	reg = <0 0x1f05d000 0 0x1000>;
> > 
> > 	xgmac_pcs: ethernet-pcs@0 {
> > 		compatible = "snps,dw-xpcs";
> > 		reg = <0>;
> > 	};
> > };
> > The platform-device is created for the mdio@1f05d000 node for which
> > the DW XPCS MI driver is loaded, which calls the
> > devm_of_mdiobus_register() in the probe() method which registers the
> > MDIO-bus and then creates the MDIO-device from the ethernet-pcs@0
> > node. The DW XPCS MDIO-device driver is attached to that MDIO-device
> > then. In such model the PCS can be supplied to the DW *MAC via the
> > "pcs-handle = &xgmac_pcs" property.
> > 
> > Regarding the current semantics it's preserved in the framework of the
> > xpcs_create_byaddr() method (former xpcs_create_mdiodev()) by means of
> > the next code snippet:
> >         if (mdiobus_is_registered_device(bus, addr)) {
> >                 mdiodev = bus->mdio_map[addr];
> >                 mdio_device_get(mdiodev);
> >         } else {
> >                 mdiodev = mdio_device_create(bus, addr);
> >                 if (IS_ERR(mdiodev))
> >                         return ERR_CAST(mdiodev);
> >         }
> > Device can be automatically created if before registering the MDIO-bus
> > the xpcs_create_byaddr() caller registered the MDIO-device board info
> > by means of the mdiobus_register_board_info() method. In addition to
> > that it's now possible to supply some custom data (custom device IDs
> > in my implementation) to the XPCS driver by means of the
> > mdio_board_info.platform_data field. See the next patch for
> > reference:
> > https://lore.kernel.org/netdev/20231205103559.9605-14-fancer.lancer@gmail.com
> > 
> > So what the difference with the Lynx PCS is that in my case the
> > MDIO-device is created automatically as a result of the DW XPCS MI
> > MDIO-bus registration. Additionally I implemented the MDIO-device
> > creation based on the MDIO-board-info, thus there won't be need in the
> > calling mdio_device_create() on each xpcs_create_mdiodev() invocation.
> > The later part isn't that important in the framework of this
> > conversation, but just so you be aware.
> 

> It's not really different, though. You can connect to the Lynx PCS both
> ways, see dpaa2_pcs_create() which also searches for a pcs-handle before
> calling lynx_pcs_create_fwnode().

Ah, right. Lynx PCS also implements the fwnode-based PCS descriptor
creation.

> What's subtly different is that we
> don't (yet) have "fsl,lynx-pcs" compatible strings in the device tree.
> So the MDIO controller will register the PCS devices as struct phy_device
> (which still have an underlying struct mdio_device). The PCS layer
> connects to the underlying struct mdio_device, and the phy_device on top
> remains unconnected to any phylib/phylink MAC driver. That is confusing,
> I should really get to adding those compatible strings to suppress the
> phy_device creation.

It hasn't been confirmed yet here
https://lore.kernel.org/netdev/na6krkoco7pmsl62dfuj2xlrvpsnod74ptpfyy6gv7dzwmowga@mzsiknjian2i/
but AFAICS it is wrong to have a PCS device registered as PHY by any
means: unmasking address in mii_bus->phy_mask or having the
of_mdiobus_child_is_phy() returned true for a DT-node. So right, a
specific compatible should be added to the PCS DT-nodes.

> 
> > Regarding the driver bind/unbind. As I said I didn't actually consider
> > that option. On the other hand my DW XPCS MDIO-device driver doesn't
> > do actual probe() or remove(). The only implemented thing is the
> > of_device_id table, which is used to assign PCS and PMA IDs if
> > required based on the DT compatible property. So I can easily drop any
> > MDIO device-driver part and parse the of_device_id table right in the
> > xpcs_create_bynode(). From that perspective my implementation won't
> > differ much from the Lynx PCS design. The only difference will be is
> > the way the MDIO-bus is created and registered. In case of Lynx PCS
> > the bus is created by the MAC-driver itself.
> 

> Nope, not true. Follow the pcs-handle in arch/arm64/boot/dts/freescale/fsl-lx2160a.dtsi.

Ah, right, I missed that case. I was referring to the cases when the
Lynx PCS MDIO node is defined as a sub-node of the MAC node.

> 
> > In my case DW XPCS MI is currently created in the framework of the
> > separate platform driver. Do you think it would be better to follow
> > the Lynx design pattern in order to get rid from the possibility of
> > the DW XPCS MI driver being unbound behind the STMMAC+XPCS couple
> > back?
> 
> I think you actually pointed out a flaw in the Lynx PCS design too.
> Actually, it is a larger flaw in the kernel. You can also unbind the
> MDIO bus which holds the phy_device, and phylib (and therefore also
> phylink) won't expect that either, so it will crash.
> 
> > In this case the Dw MAC DT-node hierarchy would look like this:
> > 
> > xgmac: ethernet@1f054000 {
> > 	compatible = "snps,dwxgmac";
> > 	reg = <0 0x1f054000 0 0x4000>;
> > 	reg-names = "stmmaceth";
> > 	ranges;
> > 
> > 	...
> > 
> > 	pcs-handle = &xgmac_pcs;
> > 
> > 	// DW XPCS MI to access the DW XPCS attached to the device
> > 	mdio@1f05d000 {
> > 		compatible = "snps,dwmac-mi";
> > 		reg = <0 0x1f05d000 0 0x1000>;
> > 
> > 		xgmac_pcs: ethernet-pcs@0 {
> > 			compatible = "snps,dw-xpcs";
> > 			reg = <0>;
> > 		};
> > 	};
> > 
> > 	// Normal MDIO-bus to access external PHYs (it's also called
> > 	// as SMA - Station Management Agent - by Synopsys)
> > 	mdio {
> > 		compatible = "snps,dwmac-mdio";
> > 		#address-cells = <1>;
> > 		#size-cells = <0>;
> > 	};
> > };
> > 
> > I actually thought to use that hardware description pattern instead,
> > but after some meditation around that I decided that having the DW
> > XPCS device defined separately from the DW MAC node seemed better at
> > least from the code separation point of view. Now I think that it
> > wasn't the best decision. DW XPCS is always attached to the DW XGMAC
> > controller. So it would be more correct having it defined as a
> > sub-node. It would also helped to avoid the platform device driver
> > bind/unbind problem.
> > 
> > What do you think? Should I re-design my patchset to be supporting the
> > design above? (After having conversion with you I am more inclined to
> > do that now than to stick with the currently implemented solution.)
> 
> I think that the placement of the "mdio" node as lateral vs subordinate
> to the "ethernet" node would have fixed the issue by mistake. We should
> be looking at it as a structural problem of the kernel instead. Don't
> let it influence what you believe should be the correct design.

Ok. Thanks for clarification. I won't move the PCS device DT-node to
being subordinate of the MAC DT-node then. Although after some more
digging into the problem I figured out that the solution still needs
to be re-designed a bit. Currently I have the DW XPCS device represented as
the nodes hierarchy:
mdio@1f05d000 {
	compatible = "snps,dwmac-mi";
	reg = <0 0x1f05d000 0 0x1000>;

	xgmac_pcs: ethernet-pcs@0 {
		compatible = "snps,dw-xpcs";
		reg = <0>;
	};
};
When I introduced such representation I assumed that it was possible
to have more than one DW XPCS devices accessible over the same MCI/APB
interface with for instance some static offset. But it turned out I
was wrong again. DW XPCS HW databook explicitly states that
port_id_i[4:0] input signal is specific to the MDIO interface only.
That signal is the MDIO-bus address of the device and it doesn't exist
for the DW XPCS devices mapped to the system IO-memory via the MCI/APB
buses. So there can't be more than one XPCS device accessible over the
same MCI/APB port and the correct way to represent the DW XPCS device
is just:
xgmac_pcs: ethernet-pcs@0 {
	compatible = "snps,dw-xpcs";
	reg = <0 0x1f05d000 0 0x1000>;
};
with no superior MI node. I'll re-design my patchset to support the
device representation above then: just create a hidden MDIO-bus in the
DW XPCS driver probe method and directly register the XPCS-device on
it. The patch for the MDIO-bus subsystem will be gone after that.

> 
> > > The pcs-rzn1-miic.c driver puts a device_link to the MAC to at least
> > > tear down the whole thing when the PCS is unbound, which is saner than
> > > crashing the kernel. I don't see the equivalent protection mechanism here?
> > 
> > You are right. I don't have any equivalent protection here. Thanks for
> > suggesting a solution.
> 
> I think that a device link between the "ethernet" device and the "mdio"
> device (controller, parent of the PHY or PCS), if the Ethernet is not a
> parent of the MDIO controller, could also solve that. But it would also
> require ACK from PHY maintainers, who may have grander plans to address
> this snag.

Ok. I'll add it in v2. Let's see what the maintainers think about
that.

Thanks for all your comments in my patchset regard. They are really
helpful.

-Serge(y)

> 
> > > Can't the xpcs continue to live without a bound driver? Having a
> > > compatible string in the OF description is perfectly fine though,
> > > and should absolutely not preclude that.
> > 
> > As I explained above Dw XPCS device can live without a bound driver
> > because the DW XPCS MDIO-driver doesn't do much but merely gets to be
> > bound based on the of_device_id table. In my case the problem is in
> > the DW XPCS MI driver which indeed can be detached. Please see my
> > long-read text above.
> 
> Yeah, common design, common problem.
Vladimir Oltean Dec. 14, 2023, noon UTC | #7
On Thu, Dec 14, 2023 at 02:54:00PM +0300, Serge Semin wrote:
> > > > The pcs-rzn1-miic.c driver puts a device_link to the MAC to at least
> > > > tear down the whole thing when the PCS is unbound, which is saner than
> > > > crashing the kernel. I don't see the equivalent protection mechanism here?
> > > 
> > > You are right. I don't have any equivalent protection here. Thanks for
> > > suggesting a solution.
> > 
> > I think that a device link between the "ethernet" device and the "mdio"
> > device (controller, parent of the PHY or PCS), if the Ethernet is not a
> > parent of the MDIO controller, could also solve that. But it would also
> > require ACK from PHY maintainers, who may have grander plans to address
> > this snag.
> 
> Ok. I'll add it in v2. Let's see what the maintainers think about
> that.

Are you not following the parallel discussion on the topic of PCS
devices having bound drivers?
https://lore.kernel.org/netdev/ZXnV%2FPk1PYxAm%2FjS@shell.armlinux.org.uk/

Sadly I don't have much spare time to join that discussion, but it looks
like you could.
Serge Semin Dec. 14, 2023, 12:28 p.m. UTC | #8
On Thu, Dec 14, 2023 at 02:00:16PM +0200, Vladimir Oltean wrote:
> On Thu, Dec 14, 2023 at 02:54:00PM +0300, Serge Semin wrote:
> > > > > The pcs-rzn1-miic.c driver puts a device_link to the MAC to at least
> > > > > tear down the whole thing when the PCS is unbound, which is saner than
> > > > > crashing the kernel. I don't see the equivalent protection mechanism here?
> > > > 
> > > > You are right. I don't have any equivalent protection here. Thanks for
> > > > suggesting a solution.
> > > 
> > > I think that a device link between the "ethernet" device and the "mdio"
> > > device (controller, parent of the PHY or PCS), if the Ethernet is not a
> > > parent of the MDIO controller, could also solve that. But it would also
> > > require ACK from PHY maintainers, who may have grander plans to address
> > > this snag.
> > 
> > Ok. I'll add it in v2. Let's see what the maintainers think about
> > that.
> 
> Are you not following the parallel discussion on the topic of PCS
> devices having bound drivers?
> https://lore.kernel.org/netdev/ZXnV%2FPk1PYxAm%2FjS@shell.armlinux.org.uk/
> 
> Sadly I don't have much spare time to join that discussion, but it looks
> like you could.

Ok. Thanks for sharing the link. At least I'll follow up the
discussion in order to pick up/wait for a solution they'll come up
with.

-Serge(y)
diff mbox series

Patch

diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
index 87cf308fc6d8..f6aa437473de 100644
--- a/drivers/net/pcs/Kconfig
+++ b/drivers/net/pcs/Kconfig
@@ -6,11 +6,11 @@ 
 menu "PCS device drivers"
 
 config PCS_XPCS
-	tristate
+	tristate "Synopsys DesignWare Ethernet XPCS"
 	select PHYLINK
 	help
-	  This module provides helper functions for Synopsys DesignWare XPCS
-	  controllers.
+	  This module provides a driver and helper functions for Synopsys
+	  DesignWare XPCS controllers.
 
 config PCS_LYNX
 	tristate
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
index ea6f56339595..183a37929b60 100644
--- a/drivers/net/pcs/pcs-xpcs.c
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -6,10 +6,14 @@ 
  * Author: Jose Abreu <Jose.Abreu@synopsys.com>
  */
 
+#include <linux/clk.h>
 #include <linux/delay.h>
-#include <linux/pcs/pcs-xpcs.h>
+#include <linux/device.h>
 #include <linux/mdio.h>
+#include <linux/module.h>
+#include <linux/pcs/pcs-xpcs.h>
 #include <linux/phylink.h>
+#include <linux/property.h>
 
 #include "pcs-xpcs.h"
 
@@ -1386,17 +1390,57 @@  static void xpcs_free_data(struct dw_xpcs *xpcs)
 	kfree(xpcs);
 }
 
+static int xpcs_init_clks(struct dw_xpcs *xpcs)
+{
+	static const char *ids[DW_XPCS_NUM_CLKS] = {
+		[DW_XPCS_CLK_CORE] = "core",
+		[DW_XPCS_CLK_PAD] = "pad",
+	};
+	struct device *dev = &xpcs->mdiodev->dev;
+	int ret, i;
+
+	for (i = 0; i < DW_XPCS_NUM_CLKS; ++i)
+		xpcs->clks[i].id = ids[i];
+
+	ret = clk_bulk_get_optional(dev, DW_XPCS_NUM_CLKS, xpcs->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get clocks\n");
+
+	ret = clk_bulk_prepare_enable(DW_XPCS_NUM_CLKS, xpcs->clks);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to enable clocks\n");
+
+	return 0;
+}
+
+static void xpcs_clear_clks(struct dw_xpcs *xpcs)
+{
+	clk_bulk_disable_unprepare(DW_XPCS_NUM_CLKS, xpcs->clks);
+
+	clk_bulk_put(DW_XPCS_NUM_CLKS, xpcs->clks);
+}
+
 static int xpcs_init_id(struct dw_xpcs *xpcs)
 {
-	u32 xpcs_id;
+	const struct dw_xpcs_info *info;
 	int i, ret;
 
-	xpcs_id = xpcs_get_id(xpcs);
+	info = device_get_match_data(&xpcs->mdiodev->dev) ?:
+	       dev_get_platdata(&xpcs->mdiodev->dev);
+	if (!info) {
+		xpcs->info.did = DW_XPCS_ID_NATIVE;
+		xpcs->info.pma = DW_XPCS_PMA_UNKNOWN;
+	} else {
+		xpcs->info = *info;
+	}
+
+	if (xpcs->info.did == DW_XPCS_ID_NATIVE)
+		xpcs->info.did = xpcs_get_id(xpcs);
 
 	for (i = 0; i < ARRAY_SIZE(xpcs_id_list); i++) {
 		const struct xpcs_id *entry = &xpcs_id_list[i];
 
-		if ((xpcs_id & entry->mask) != entry->id)
+		if ((xpcs->info.did & entry->mask) != entry->id)
 			continue;
 
 		xpcs->id = entry;
@@ -1436,21 +1480,32 @@  static struct dw_xpcs *xpcs_create(struct mdio_device *mdiodev,
 	struct dw_xpcs *xpcs;
 	int ret;
 
+	ret = device_attach(&mdiodev->dev);
+	if (ret < 0 && ret != -ENODEV)
+		return ERR_PTR(ret);
+
 	xpcs = xpcs_create_data(mdiodev);
 	if (IS_ERR(xpcs))
 		return xpcs;
 
+	ret = xpcs_init_clks(xpcs);
+	if (ret)
+		goto out_free_data;
+
 	ret = xpcs_init_id(xpcs);
 	if (ret)
-		goto out;
+		goto out_clear_clks;
 
 	ret = xpcs_init_iface(xpcs, interface);
 	if (ret)
-		goto out;
+		goto out_clear_clks;
 
 	return xpcs;
 
-out:
+out_clear_clks:
+	xpcs_clear_clks(xpcs);
+
+out_free_data:
 	xpcs_free_data(xpcs);
 
 	return ERR_PTR(ret);
@@ -1489,8 +1544,51 @@  void xpcs_destroy(struct dw_xpcs *xpcs)
 
 	mdio_device_put(xpcs->mdiodev);
 
+	xpcs_clear_clks(xpcs);
+
 	xpcs_free_data(xpcs);
 }
 EXPORT_SYMBOL_GPL(xpcs_destroy);
 
+DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_UNKNOWN);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen1_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN1_3G);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_3G);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_6G);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G);
+DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G);
+
+static const struct of_device_id xpcs_of_ids[] = {
+	{ .compatible = "snps,dw-xpcs", .data = &xpcs_generic },
+	{ .compatible = "snps,dw-xpcs-gen1-3g", .data = &xpcs_pma_gen1_3g },
+	{ .compatible = "snps,dw-xpcs-gen2-3g", .data = &xpcs_pma_gen2_3g },
+	{ .compatible = "snps,dw-xpcs-gen2-6g", .data = &xpcs_pma_gen2_6g },
+	{ .compatible = "snps,dw-xpcs-gen4-3g", .data = &xpcs_pma_gen4_3g },
+	{ .compatible = "snps,dw-xpcs-gen4-6g", .data = &xpcs_pma_gen4_6g },
+	{ .compatible = "snps,dw-xpcs-gen5-10g", .data = &xpcs_pma_gen5_10g },
+	{ .compatible = "snps,dw-xpcs-gen5-12g", .data = &xpcs_pma_gen5_12g },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, xpcs_of_ids);
+
+static struct mdio_device_id __maybe_unused xpcs_mdio_ids[] = {
+	{ DW_XPCS_ID, DW_XPCS_ID_MASK },
+	{ NXP_SJA1105_XPCS_ID, DW_XPCS_ID_MASK },
+	{ NXP_SJA1110_XPCS_ID, DW_XPCS_ID_MASK },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, xpcs_mdio_ids);
+
+static struct mdio_driver xpcs_driver = {
+	.mdiodrv.driver = {
+		.name = "dwxpcs",
+		.of_match_table = xpcs_of_ids,
+		.probe_type = PROBE_FORCE_SYNCHRONOUS,
+	},
+};
+mdio_module_driver(xpcs_driver);
+
+MODULE_DESCRIPTION("DWC Ethernet XPCS platform driver");
+MODULE_AUTHOR("Jose Abreu <Jose.Abreu@synopsys.com>");
 MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/pcs/pcs-xpcs.h b/drivers/net/pcs/pcs-xpcs.h
index 369e9196f45a..45fea2641d23 100644
--- a/drivers/net/pcs/pcs-xpcs.h
+++ b/drivers/net/pcs/pcs-xpcs.h
@@ -6,6 +6,9 @@ 
  * Author: Jose Abreu <Jose.Abreu@synopsys.com>
  */
 
+#include <linux/bits.h>
+#include <linux/pcs/pcs-xpcs.h>
+
 /* Vendor regs access */
 #define DW_VENDOR			BIT(15)
 
@@ -117,6 +120,9 @@ 
 /* VR MII EEE Control 1 defines */
 #define DW_VR_MII_EEE_TRN_LPI		BIT(0)	/* Transparent Mode Enable */
 
+#define DW_XPCS_INFO_DECLARE(_name, _did, _pma)			\
+	static const struct dw_xpcs_info _name = { .did = _did, .pma = _pma }
+
 int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg);
 int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val);
 int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg);
diff --git a/include/linux/pcs/pcs-xpcs.h b/include/linux/pcs/pcs-xpcs.h
index 8dfe90295f12..53adbffb4c0a 100644
--- a/include/linux/pcs/pcs-xpcs.h
+++ b/include/linux/pcs/pcs-xpcs.h
@@ -7,9 +7,12 @@ 
 #ifndef __LINUX_PCS_XPCS_H
 #define __LINUX_PCS_XPCS_H
 
+#include <linux/clk.h>
+#include <linux/mdio.h>
 #include <linux/phy.h>
 #include <linux/phylink.h>
 
+#define DW_XPCS_ID_NATIVE		0x00000000
 #define NXP_SJA1105_XPCS_ID		0x00000010
 #define NXP_SJA1110_XPCS_ID		0x00000020
 #define DW_XPCS_ID			0x7996ced0
@@ -30,9 +33,33 @@ 
 
 struct xpcs_id;
 
+enum dw_xpcs_pma {
+	DW_XPCS_PMA_UNKNOWN = 0,
+	DW_XPCS_PMA_GEN1_3G,
+	DW_XPCS_PMA_GEN2_3G,
+	DW_XPCS_PMA_GEN2_6G,
+	DW_XPCS_PMA_GEN4_3G,
+	DW_XPCS_PMA_GEN4_6G,
+	DW_XPCS_PMA_GEN5_10G,
+	DW_XPCS_PMA_GEN5_12G,
+};
+
+enum dw_xpcs_clock {
+	DW_XPCS_CLK_CORE,
+	DW_XPCS_CLK_PAD,
+	DW_XPCS_NUM_CLKS,
+};
+
+struct dw_xpcs_info {
+	u32 did;
+	u32 pma;
+};
+
 struct dw_xpcs {
 	struct mdio_device *mdiodev;
+	struct dw_xpcs_info info;
 	const struct xpcs_id *id;
+	struct clk_bulk_data clks[DW_XPCS_NUM_CLKS];
 	struct phylink_pcs pcs;
 	phy_interface_t interface;
 	int dev_flag;