Linux CAN drivers development
 help / color / mirror / Atom feed
* Re: [PATCH] can: ctucanfd: handle skb allocation failure
From: Vincent Mailhol @ 2025-01-15  0:40 UTC (permalink / raw)
  To: Fedor Pchelkin, Pavel Pisa, Marc Kleine-Budde
  Cc: Ondrej Ille, Martin Jerabek, linux-can, linux-kernel, lvc-project,
	stable
In-Reply-To: <20250114152138.139580-1-pchelkin@ispras.ru>

On 15/01/2025 at 00:21, Fedor Pchelkin wrote:
> If skb allocation fails, the pointer to struct can_frame is NULL. This
> is actually handled everywhere inside ctucan_err_interrupt() except for
> the only place.
> 
> Add the missed NULL check.
> 
> Found by Linux Verification Center (linuxtesting.org) with SVACE static
> analysis tool.
> 
> Fixes: 2dcb8e8782d8 ("can: ctucanfd: add support for CTU CAN FD open-source IP core - bus independent part.")
> Cc: stable@vger.kernel.org
> Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>

Reviewed-by: Vincent Mailhol <mailhol.vincent@wanadoo.fr>

Yours sincerely,
Vincent Mailhol


^ permalink raw reply

* Re: [PATCH] net: Convert proto_ops::getname to sockaddr_storage
From: Allison Henderson @ 2025-01-14 20:59 UTC (permalink / raw)
  To: kees@kernel.org, kuba@kernel.org
  Cc: kirjanov@gmail.com, socketcan@hartkopp.net,
	linux-cifs@vger.kernel.org, davem@davemloft.net, Michael Christie,
	jreuter@yaina.de, linux-can@vger.kernel.org, trondmy@kernel.org,
	Dai Ngo, ying.xue@windriver.com, kbusch@kernel.org,
	linux-bluetooth@vger.kernel.org, kaishen@linux.alibaba.com,
	thuth@redhat.com, linux-nfs@vger.kernel.org,
	nabijaczleweli@nabijaczleweli.xyz, andersson@kernel.org,
	okorniev@redhat.com, jmaloy@redhat.com, guwen@linux.alibaba.com,
	joseph.qi@linux.alibaba.com, tom@talpey.com, paul@paul-moore.com,
	ignat@cloudflare.com, tonylu@linux.alibaba.com,
	wintera@linux.ibm.com, isdn@linux-pingi.de, mark@fasheh.com,
	gfs2@lists.linux.dev, linux-scsi@vger.kernel.org,
	linux-hams@vger.kernel.org, alibuda@linux.alibaba.com,
	lucien.xin@gmail.com, arnd@arndb.de, benve@cisco.com,
	marcel@holtmann.org, mlombard@redhat.com, yajun.deng@linux.dev,
	sagi@grimberg.me, bmt@zurich.ibm.com,
	manivannan.sadhasivam@linaro.org, syzkaller@googlegroups.com,
	open-iscsi@googlegroups.com, konradybcio@kernel.org,
	linux-sctp@vger.kernel.org, o.rempel@pengutronix.de,
	syzbot+d7ce59b06b3eb14fd218@syzkaller.appspotmail.com,
	takedakn@nttdata.co.jp, mostrows@earthlink.net, jgg@ziepe.ca,
	gnault@redhat.com, chengyou@linux.alibaba.com,
	luiz.dentz@gmail.com, James.Bottomley@HansenPartnership.com,
	kch@nvidia.com, f6bvp@free.fr, marcelo.leitner@gmail.com,
	edumazet@google.com, leon@kernel.org, lduncan@suse.com,
	yunchuan@nfschina.com, ms@dev.tdt.de, gouhao@uniontech.com,
	linux-arm-msm@vger.kernel.org, penguin-kernel@I-love.SAKURA.ne.jp,
	jaka@linux.ibm.com, jiri@resnulli.us, rds-devel@oss.oracle.com,
	netdev@vger.kernel.org, ocfs2-devel@lists.linux.dev,
	jlbec@evilplan.org, mhal@rbox.co, lukas.bulwahn@gmail.com,
	doug@schmorgal.com, anna@kernel.org, andrew+netdev@lunn.ch,
	twinkler@linux.ibm.com, johan.hedberg@gmail.com,
	vincent.ldev@duvert.net, cleech@redhat.com, error27@gmail.com,
	dhowells@redhat.com, courmisch@gmail.com, jmorris@namei.org,
	virtualization@lists.linux.dev, pabeni@redhat.com,
	yinghsu@chromium.org, waterman@eecs.berkeley.edu,
	horms@kernel.org, linux-x25@vger.kernel.org,
	linux-hardening@vger.kernel.org, willemdebruijn.kernel@gmail.com,
	tparkin@katalix.com, linux-security-module@vger.kernel.org,
	krzk@kernel.org, kernel@pengutronix.de,
	target-devel@vger.kernel.org, robin@protonic.nl,
	jchapman@katalix.com, hch@lst.de, jlayton@kernel.org,
	linux-kernel@vger.kernel.org, wenjia@linux.ibm.com,
	quic_abchauha@quicinc.com, aahringo@redhat.com,
	atteh.mailbox@gmail.com, linkinjeon@kernel.org, axboe@kernel.dk,
	sfrench@samba.org, sgarzare@redhat.com, serge@hallyn.com,
	neescoba@cisco.com, akpm@linux-foundation.org,
	constant.lee@samsung.com, senozhatsky@chromium.org,
	linux-nvme@lists.infradead.org, kernelxing@tencent.com,
	Martin Petersen, dsahern@kernel.org,
	tipc-discussion@lists.sourceforge.net, almasrymina@google.com,
	kuniyu@amazon.com, mkl@pengutronix.de, mgurtovoy@nvidia.com,
	linux-s390@vger.kernel.org, teigland@redhat.com,
	andrew.shadura@collabora.co.uk, linux-rdma@vger.kernel.org,
	neilb@suse.de, v4bel@theori.io, palmer@dabbelt.com,
	Chuck Lever III
In-Reply-To: <20241217023417.work.145-kees@kernel.org>

On Mon, 2024-12-16 at 18:34 -0800, Kees Cook wrote:
> The proto_ops::getname callback was long ago backed by sockaddr_storage,
> but the replacement of it for sockaddr was never done. Plumb it through
> all the getname() callbacks, adjust prototypes, and fix casts.
> 
> There are a few cases where the backing object is _not_ a sockaddr_storage
> and converting it looks painful. In those cases, they use a cast to
> struct sockaddr_storage. They appear well bounds-checked, so the risk
> is no worse that we have currently.
> 
> Other casts to sockaddr are removed, though to avoid spilling this
> change into BPF (which becomes a much larger set of changes), cast the
> sockaddr_storage instances there to sockaddr for the time being.
> 
> In theory this could be split up into per-caller patches that add more
> casts that all later get removed, but it seemed like there are few
> enough callers that it seems feasible to do this in a single patch. Most
> conversions are mechanical, so review should be fairly easy. (Famous
> last words.)
> 
> Signed-off-by: Kees Cook <kees@kernel.org>
> ---
>  drivers/infiniband/hw/erdma/erdma_cm.h        |  4 +-
>  drivers/infiniband/hw/usnic/usnic_transport.c | 16 +++---
>  drivers/infiniband/sw/siw/siw_cm.h            |  4 +-
>  drivers/isdn/mISDN/socket.c                   |  2 +-
>  drivers/net/ppp/pppoe.c                       |  4 +-
>  drivers/net/ppp/pptp.c                        |  4 +-
>  drivers/nvme/host/tcp.c                       |  2 +-
>  drivers/nvme/target/tcp.c                     |  6 +--
>  drivers/scsi/iscsi_tcp.c                      | 18 +++----
>  drivers/soc/qcom/qmi_interface.c              |  2 +-
>  drivers/target/iscsi/iscsi_target_login.c     | 51 +++++++++----------
>  fs/dlm/lowcomms.c                             |  2 +-
>  fs/nfs/nfs4client.c                           |  4 +-
>  fs/ocfs2/cluster/tcp.c                        | 26 +++++-----
>  fs/smb/server/connection.h                    |  2 +-
>  fs/smb/server/mgmt/tree_connect.c             |  2 +-
>  fs/smb/server/transport_ipc.c                 |  4 +-
>  fs/smb/server/transport_ipc.h                 |  4 +-
>  fs/smb/server/transport_tcp.c                 |  6 +--
>  include/linux/net.h                           |  6 +--
>  include/linux/sunrpc/clnt.h                   |  4 +-
>  include/net/inet_common.h                     |  2 +-
>  include/net/ipv6.h                            |  2 +-
>  include/net/sock.h                            |  3 +-
>  net/appletalk/ddp.c                           |  2 +-
>  net/atm/pvc.c                                 |  2 +-
>  net/atm/svc.c                                 |  2 +-
>  net/ax25/af_ax25.c                            |  4 +-
>  net/bluetooth/hci_sock.c                      |  2 +-
>  net/bluetooth/iso.c                           |  6 +--
>  net/bluetooth/l2cap_sock.c                    |  6 +--
>  net/bluetooth/rfcomm/sock.c                   |  3 +-
>  net/bluetooth/sco.c                           |  6 +--
>  net/can/isotp.c                               |  3 +-
>  net/can/j1939/socket.c                        |  2 +-
>  net/can/raw.c                                 |  2 +-
>  net/core/sock.c                               |  4 +-
>  net/ipv4/af_inet.c                            |  2 +-
>  net/ipv6/af_inet6.c                           |  2 +-
>  net/iucv/af_iucv.c                            |  6 +--
>  net/l2tp/l2tp_ip.c                            |  2 +-
>  net/l2tp/l2tp_ip6.c                           |  2 +-
>  net/l2tp/l2tp_ppp.c                           |  2 +-
>  net/llc/af_llc.c                              |  2 +-
>  net/netlink/af_netlink.c                      |  4 +-
>  net/netrom/af_netrom.c                        |  4 +-
>  net/nfc/llcp_sock.c                           |  4 +-
>  net/packet/af_packet.c                        | 15 +++---
>  net/phonet/socket.c                           | 10 ++--
>  net/qrtr/af_qrtr.c                            |  2 +-
>  net/qrtr/ns.c                                 |  2 +-
>  net/rds/af_rds.c                              |  2 +-
>  net/rose/af_rose.c                            |  4 +-
>  net/sctp/ipv6.c                               |  2 +-
>  net/smc/af_smc.c                              |  2 +-
>  net/smc/smc.h                                 |  2 +-
>  net/smc/smc_clc.c                             |  2 +-
>  net/socket.c                                  | 10 ++--
>  net/sunrpc/clnt.c                             |  9 ++--
>  net/sunrpc/svcsock.c                          |  8 +--
>  net/sunrpc/xprtsock.c                         |  4 +-
>  net/tipc/socket.c                             |  2 +-
>  net/unix/af_unix.c                            |  9 ++--
>  net/vmw_vsock/af_vsock.c                      |  2 +-
>  net/x25/af_x25.c                              |  2 +-
>  security/tomoyo/network.c                     |  3 +-
>  66 files changed, 173 insertions(+), 173 deletions(-)
> 
> diff --git a/drivers/infiniband/hw/erdma/erdma_cm.h b/drivers/infiniband/hw/erdma/erdma_cm.h
> index a26d80770188..4e46ba491d5c 100644
> --- a/drivers/infiniband/hw/erdma/erdma_cm.h
> +++ b/drivers/infiniband/hw/erdma/erdma_cm.h
> @@ -141,12 +141,12 @@ struct erdma_cm_work {
>  
>  static inline int getname_peer(struct socket *s, struct sockaddr_storage *a)
>  {
> -	return s->ops->getname(s, (struct sockaddr *)a, 1);
> +	return s->ops->getname(s, a, 1);
>  }
>  
>  static inline int getname_local(struct socket *s, struct sockaddr_storage *a)
>  {
> -	return s->ops->getname(s, (struct sockaddr *)a, 0);
> +	return s->ops->getname(s, a, 0);
>  }
>  
>  int erdma_connect(struct iw_cm_id *id, struct iw_cm_conn_param *param);
> diff --git a/drivers/infiniband/hw/usnic/usnic_transport.c b/drivers/infiniband/hw/usnic/usnic_transport.c
> index dc37066900a5..7c38abc25671 100644
> --- a/drivers/infiniband/hw/usnic/usnic_transport.c
> +++ b/drivers/infiniband/hw/usnic/usnic_transport.c
> @@ -174,24 +174,24 @@ int usnic_transport_sock_get_addr(struct socket *sock, int *proto,
>  					uint32_t *addr, uint16_t *port)
>  {
>  	int err;
> -	struct sockaddr_in sock_addr;
> +	union {
> +		struct sockaddr_storage storage;
> +		struct sockaddr_in sock_addr;
> +	} u;
>  
> -	err = sock->ops->getname(sock,
> -				(struct sockaddr *)&sock_addr,
> -				0);
> +	err = sock->ops->getname(sock, &u.storage, 0);
>  	if (err < 0)
>  		return err;
>  
> -	if (sock_addr.sin_family != AF_INET)
> +	if (u.sock_addr.sin_family != AF_INET)
>  		return -EINVAL;
>  
>  	if (proto)
>  		*proto = sock->sk->sk_protocol;
>  	if (port)
> -		*port = ntohs(((struct sockaddr_in *)&sock_addr)->sin_port);
> +		*port = ntohs(u.sock_addr.sin_port);
>  	if (addr)
> -		*addr = ntohl(((struct sockaddr_in *)
> -					&sock_addr)->sin_addr.s_addr);
> +		*addr = ntohl(u.sock_addr.sin_addr.s_addr);
>  
>  	return 0;
>  }
> diff --git a/drivers/infiniband/sw/siw/siw_cm.h b/drivers/infiniband/sw/siw/siw_cm.h
> index 7011c8a8ee7b..804559be83d4 100644
> --- a/drivers/infiniband/sw/siw/siw_cm.h
> +++ b/drivers/infiniband/sw/siw/siw_cm.h
> @@ -94,12 +94,12 @@ struct siw_cm_work {
>  
>  static inline int getname_peer(struct socket *s, struct sockaddr_storage *a)
>  {
> -	return s->ops->getname(s, (struct sockaddr *)a, 1);
> +	return s->ops->getname(s, a, 1);
>  }
>  
>  static inline int getname_local(struct socket *s, struct sockaddr_storage *a)
>  {
> -	return s->ops->getname(s, (struct sockaddr *)a, 0);
> +	return s->ops->getname(s, a, 0);
>  }
>  
>  static inline int ksock_recv(struct socket *sock, char *buf, size_t size,
> diff --git a/drivers/isdn/mISDN/socket.c b/drivers/isdn/mISDN/socket.c
> index b215b28cad7b..2a3bcbf6d15b 100644
> --- a/drivers/isdn/mISDN/socket.c
> +++ b/drivers/isdn/mISDN/socket.c
> @@ -549,7 +549,7 @@ data_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
>  }
>  
>  static int
> -data_sock_getname(struct socket *sock, struct sockaddr *addr,
> +data_sock_getname(struct socket *sock, struct sockaddr_storage *addr,
>  		  int peer)
>  {
>  	struct sockaddr_mISDN	*maddr = (struct sockaddr_mISDN *) addr;
> diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c
> index 2ea4f4890d23..139def6a7128 100644
> --- a/drivers/net/ppp/pppoe.c
> +++ b/drivers/net/ppp/pppoe.c
> @@ -717,8 +717,8 @@ static int pppoe_connect(struct socket *sock, struct sockaddr *uservaddr,
>  	goto end;
>  }
>  
> -static int pppoe_getname(struct socket *sock, struct sockaddr *uaddr,
> -		  int peer)
> +static int pppoe_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +			 int peer)
>  {
>  	int len = sizeof(struct sockaddr_pppox);
>  	struct sockaddr_pppox sp;
> diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c
> index 689687bd2574..6d090008a34e 100644
> --- a/drivers/net/ppp/pptp.c
> +++ b/drivers/net/ppp/pptp.c
> @@ -479,8 +479,8 @@ static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr,
>  	return error;
>  }
>  
> -static int pptp_getname(struct socket *sock, struct sockaddr *uaddr,
> -	int peer)
> +static int pptp_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +			int peer)
>  {
>  	int len = sizeof(struct sockaddr_pppox);
>  	struct sockaddr_pppox sp;
> diff --git a/drivers/nvme/host/tcp.c b/drivers/nvme/host/tcp.c
> index 28c76a3e1bd2..06853bf1ee8c 100644
> --- a/drivers/nvme/host/tcp.c
> +++ b/drivers/nvme/host/tcp.c
> @@ -2642,7 +2642,7 @@ static int nvme_tcp_get_address(struct nvme_ctrl *ctrl, char *buf, int size)
>  
>  	mutex_lock(&queue->queue_lock);
>  
> -	ret = kernel_getsockname(queue->sock, (struct sockaddr *)&src_addr);
> +	ret = kernel_getsockname(queue->sock, &src_addr);
>  	if (ret > 0) {
>  		if (len > 0)
>  			len--; /* strip trailing newline */
> diff --git a/drivers/nvme/target/tcp.c b/drivers/nvme/target/tcp.c
> index df24244fb820..87324c723ed4 100644
> --- a/drivers/nvme/target/tcp.c
> +++ b/drivers/nvme/target/tcp.c
> @@ -1689,13 +1689,11 @@ static int nvmet_tcp_set_queue_sock(struct nvmet_tcp_queue *queue)
>  	struct inet_sock *inet = inet_sk(sock->sk);
>  	int ret;
>  
> -	ret = kernel_getsockname(sock,
> -		(struct sockaddr *)&queue->sockaddr);
> +	ret = kernel_getsockname(sock, &queue->sockaddr);
>  	if (ret < 0)
>  		return ret;
>  
> -	ret = kernel_getpeername(sock,
> -		(struct sockaddr *)&queue->sockaddr_peer);
> +	ret = kernel_getpeername(sock, &queue->sockaddr_peer);
>  	if (ret < 0)
>  		return ret;
>  
> diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c
> index c708e1059638..0a457f8a5062 100644
> --- a/drivers/scsi/iscsi_tcp.c
> +++ b/drivers/scsi/iscsi_tcp.c
> @@ -794,7 +794,7 @@ static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
>  	struct iscsi_conn *conn = cls_conn->dd_data;
>  	struct iscsi_sw_tcp_conn *tcp_sw_conn;
>  	struct iscsi_tcp_conn *tcp_conn;
> -	struct sockaddr_in6 addr;
> +	struct sockaddr_storage addr;
>  	struct socket *sock;
>  	int rc;
>  
> @@ -825,19 +825,16 @@ static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn,
>  		}
>  
>  		if (param == ISCSI_PARAM_LOCAL_PORT)
> -			rc = kernel_getsockname(sock,
> -						(struct sockaddr *)&addr);
> +			rc = kernel_getsockname(sock, &addr);
>  		else
> -			rc = kernel_getpeername(sock,
> -						(struct sockaddr *)&addr);
> +			rc = kernel_getpeername(sock, &addr);
>  sock_unlock:
>  		mutex_unlock(&tcp_sw_conn->sock_lock);
>  		iscsi_put_conn(conn->cls_conn);
>  		if (rc < 0)
>  			return rc;
>  
> -		return iscsi_conn_get_addr_param((struct sockaddr_storage *)
> -						 &addr, param, buf);
> +		return iscsi_conn_get_addr_param(&addr, param, buf);
>  	default:
>  		return iscsi_conn_get_param(cls_conn, param, buf);
>  	}
> @@ -853,7 +850,7 @@ static int iscsi_sw_tcp_host_get_param(struct Scsi_Host *shost,
>  	struct iscsi_conn *conn;
>  	struct iscsi_tcp_conn *tcp_conn;
>  	struct iscsi_sw_tcp_conn *tcp_sw_conn;
> -	struct sockaddr_in6 addr;
> +	struct sockaddr_storage addr;
>  	struct socket *sock;
>  	int rc;
>  
> @@ -883,14 +880,13 @@ static int iscsi_sw_tcp_host_get_param(struct Scsi_Host *shost,
>  		if (!sock)
>  			rc = -ENOTCONN;
>  		else
> -			rc = kernel_getsockname(sock, (struct sockaddr *)&addr);
> +			rc = kernel_getsockname(sock, &addr);
>  		mutex_unlock(&tcp_sw_conn->sock_lock);
>  		iscsi_put_conn(conn->cls_conn);
>  		if (rc < 0)
>  			return rc;
>  
> -		return iscsi_conn_get_addr_param((struct sockaddr_storage *)
> -						 &addr,
> +		return iscsi_conn_get_addr_param(&addr,
>  						 (enum iscsi_param)param, buf);
>  	default:
>  		return iscsi_host_get_param(shost, param, buf);
> diff --git a/drivers/soc/qcom/qmi_interface.c b/drivers/soc/qcom/qmi_interface.c
> index bc6d6379d8b1..977cdf0f6598 100644
> --- a/drivers/soc/qcom/qmi_interface.c
> +++ b/drivers/soc/qcom/qmi_interface.c
> @@ -593,7 +593,7 @@ static struct socket *qmi_sock_create(struct qmi_handle *qmi,
>  	if (ret < 0)
>  		return ERR_PTR(ret);
>  
> -	ret = kernel_getsockname(sock, (struct sockaddr *)sq);
> +	ret = kernel_getsockname(sock, (struct sockaddr_storage *)sq);
>  	if (ret < 0) {
>  		sock_release(sock);
>  		return ERR_PTR(ret);
> diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c
> index 90b870f234f0..9fcbfba43035 100644
> --- a/drivers/target/iscsi/iscsi_target_login.c
> +++ b/drivers/target/iscsi/iscsi_target_login.c
> @@ -907,8 +907,11 @@ int iscsi_target_setup_login_socket(
>  int iscsit_accept_np(struct iscsi_np *np, struct iscsit_conn *conn)
>  {
>  	struct socket *new_sock, *sock = np->np_socket;
> -	struct sockaddr_in sock_in;
> -	struct sockaddr_in6 sock_in6;
> +	union {
> +		struct sockaddr_storage storage;
> +		struct sockaddr_in sock_in;
> +		struct sockaddr_in6 sock_in6;
> +	} u;
>  	int rc;
>  
>  	rc = kernel_accept(sock, &new_sock, 0);
> @@ -919,47 +922,43 @@ int iscsit_accept_np(struct iscsi_np *np, struct iscsit_conn *conn)
>  	conn->login_family = np->np_sockaddr.ss_family;
>  
>  	if (np->np_sockaddr.ss_family == AF_INET6) {
> -		memset(&sock_in6, 0, sizeof(struct sockaddr_in6));
> +		memset(&u.sock_in6, 0, sizeof(struct sockaddr_in6));
>  
> -		rc = conn->sock->ops->getname(conn->sock,
> -				(struct sockaddr *)&sock_in6, 1);
> +		rc = conn->sock->ops->getname(conn->sock, &u.storage, 1);
>  		if (rc >= 0) {
> -			if (!ipv6_addr_v4mapped(&sock_in6.sin6_addr)) {
> -				memcpy(&conn->login_sockaddr, &sock_in6, sizeof(sock_in6));
> +			if (!ipv6_addr_v4mapped(&u.sock_in6.sin6_addr)) {
> +				memcpy(&conn->login_sockaddr, &u.sock_in6, sizeof(u.sock_in6));
>  			} else {
>  				/* Pretend to be an ipv4 socket */
> -				sock_in.sin_family = AF_INET;
> -				sock_in.sin_port = sock_in6.sin6_port;
> -				memcpy(&sock_in.sin_addr, &sock_in6.sin6_addr.s6_addr32[3], 4);
> -				memcpy(&conn->login_sockaddr, &sock_in, sizeof(sock_in));
> +				u.sock_in.sin_family = AF_INET;
> +				u.sock_in.sin_port = u.sock_in6.sin6_port;
> +				memcpy(&u.sock_in.sin_addr, &u.sock_in6.sin6_addr.s6_addr32[3], 4);
> +				memcpy(&conn->login_sockaddr, &u.sock_in, sizeof(u.sock_in));
>  			}
>  		}
>  
> -		rc = conn->sock->ops->getname(conn->sock,
> -				(struct sockaddr *)&sock_in6, 0);
> +		rc = conn->sock->ops->getname(conn->sock, &u.storage, 0);
>  		if (rc >= 0) {
> -			if (!ipv6_addr_v4mapped(&sock_in6.sin6_addr)) {
> -				memcpy(&conn->local_sockaddr, &sock_in6, sizeof(sock_in6));
> +			if (!ipv6_addr_v4mapped(&u.sock_in6.sin6_addr)) {
> +				memcpy(&conn->local_sockaddr, &u.sock_in6, sizeof(u.sock_in6));
>  			} else {
>  				/* Pretend to be an ipv4 socket */
> -				sock_in.sin_family = AF_INET;
> -				sock_in.sin_port = sock_in6.sin6_port;
> -				memcpy(&sock_in.sin_addr, &sock_in6.sin6_addr.s6_addr32[3], 4);
> -				memcpy(&conn->local_sockaddr, &sock_in, sizeof(sock_in));
> +				u.sock_in.sin_family = AF_INET;
> +				u.sock_in.sin_port = u.sock_in6.sin6_port;
> +				memcpy(&u.sock_in.sin_addr, &u.sock_in6.sin6_addr.s6_addr32[3], 4);
> +				memcpy(&conn->local_sockaddr, &u.sock_in, sizeof(u.sock_in));
>  			}
>  		}
>  	} else {
> -		memset(&sock_in, 0, sizeof(struct sockaddr_in));
> +		memset(&u.sock_in, 0, sizeof(struct sockaddr_in));
>  
> -		rc = conn->sock->ops->getname(conn->sock,
> -				(struct sockaddr *)&sock_in, 1);
> +		rc = conn->sock->ops->getname(conn->sock, &u.storage, 1);
>  		if (rc >= 0)
> -			memcpy(&conn->login_sockaddr, &sock_in, sizeof(sock_in));
> +			memcpy(&conn->login_sockaddr, &u.sock_in, sizeof(u.sock_in));
>  
> -		rc = conn->sock->ops->getname(conn->sock,
> -				(struct sockaddr *)&sock_in, 0);
> +		rc = conn->sock->ops->getname(conn->sock, &u.storage, 0);
>  		if (rc >= 0)
> -			memcpy(&conn->local_sockaddr, &sock_in, sizeof(sock_in));
> +			memcpy(&conn->local_sockaddr, &u.sock_in, sizeof(u.sock_in));
>  	}
>  
>  	return 0;
> diff --git a/fs/dlm/lowcomms.c b/fs/dlm/lowcomms.c
> index df40c3fd1070..7f232936e73c 100644
> --- a/fs/dlm/lowcomms.c
> +++ b/fs/dlm/lowcomms.c
> @@ -993,7 +993,7 @@ static int accept_from_sock(void)
>  
>  	/* Get the connected socket's peer */
>  	memset(&peeraddr, 0, sizeof(peeraddr));
> -	len = newsock->ops->getname(newsock, (struct sockaddr *)&peeraddr, 2);
> +	len = newsock->ops->getname(newsock, &peeraddr, 2);
>  	if (len < 0) {
>  		result = -ECONNABORTED;
>  		goto accept_err;
> diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
> index 83378f69b35e..a7428367a526 100644
> --- a/fs/nfs/nfs4client.c
> +++ b/fs/nfs/nfs4client.c
> @@ -248,7 +248,7 @@ struct nfs_client *nfs4_alloc_client(const struct nfs_client_initdata *cl_init)
>  		struct sockaddr_storage cb_addr;
>  		struct sockaddr *sap = (struct sockaddr *)&cb_addr;
>  
> -		err = rpc_localaddr(clp->cl_rpcclient, sap, sizeof(cb_addr));
> +		err = rpc_localaddr(clp->cl_rpcclient, &cb_addr, sizeof(cb_addr));
>  		if (err < 0)
>  			goto error;
>  		err = rpc_ntop(sap, buf, sizeof(buf));
> @@ -1352,7 +1352,7 @@ int nfs4_update_server(struct nfs_server *server, const char *hostname,
>  	if (error != 0)
>  		return error;
>  
> -	error = rpc_localaddr(clnt, localaddr, sizeof(address));
> +	error = rpc_localaddr(clnt, &address, sizeof(address));
>  	if (error != 0)
>  		return error;
>  
> diff --git a/fs/ocfs2/cluster/tcp.c b/fs/ocfs2/cluster/tcp.c
> index 2b8fa3e782fb..bb8ccd3a222f 100644
> --- a/fs/ocfs2/cluster/tcp.c
> +++ b/fs/ocfs2/cluster/tcp.c
> @@ -1779,7 +1779,10 @@ int o2net_register_hb_callbacks(void)
>  static int o2net_accept_one(struct socket *sock, int *more)
>  {
>  	int ret;
> -	struct sockaddr_in sin;
> +	union {
> +		struct sockaddr_storage storage;
> +		struct sockaddr_in sin;
> +	} u;
>  	struct socket *new_sock = NULL;
>  	struct o2nm_node *node = NULL;
>  	struct o2nm_node *local_node = NULL;
> @@ -1815,15 +1818,14 @@ static int o2net_accept_one(struct socket *sock, int *more)
>  	tcp_sock_set_nodelay(new_sock->sk);
>  	tcp_sock_set_user_timeout(new_sock->sk, O2NET_TCP_USER_TIMEOUT);
>  
> -	ret = new_sock->ops->getname(new_sock, (struct sockaddr *) &sin, 1);
> +	ret = new_sock->ops->getname(new_sock, &u.storage, 1);
>  	if (ret < 0)
>  		goto out;
>  
> -	node = o2nm_get_node_by_ip(sin.sin_addr.s_addr);
> +	node = o2nm_get_node_by_ip(u.sin.sin_addr.s_addr);
>  	if (node == NULL) {
> -		printk(KERN_NOTICE "o2net: Attempt to connect from unknown "
> -		       "node at %pI4:%d\n", &sin.sin_addr.s_addr,
> -		       ntohs(sin.sin_port));
> +		printk(KERN_NOTICE "o2net: Attempt to connect from unknown node at %pI4:%d\n",
> +		       &u.sin.sin_addr.s_addr, ntohs(u.sin.sin_port));
>  		ret = -EINVAL;
>  		goto out;
>  	}
> @@ -1838,8 +1840,8 @@ static int o2net_accept_one(struct socket *sock, int *more)
>  					&(local_node->nd_ipv4_address),
>  					ntohs(local_node->nd_ipv4_port),
>  					node->nd_name,
> -					node->nd_num, &sin.sin_addr.s_addr,
> -					ntohs(sin.sin_port));
> +					node->nd_num, &u.sin.sin_addr.s_addr,
> +					ntohs(u.sin.sin_port));
>  		ret = -EINVAL;
>  		goto out;
>  	}
> @@ -1849,8 +1851,8 @@ static int o2net_accept_one(struct socket *sock, int *more)
>  	if (!o2hb_check_node_heartbeating_from_callback(node->nd_num)) {
>  		mlog(ML_CONN, "attempt to connect from node '%s' at "
>  		     "%pI4:%d but it isn't heartbeating\n",
> -		     node->nd_name, &sin.sin_addr.s_addr,
> -		     ntohs(sin.sin_port));
> +		     node->nd_name, &u.sin.sin_addr.s_addr,
> +		     ntohs(u.sin.sin_port));
>  		ret = -EINVAL;
>  		goto out;
>  	}
> @@ -1866,8 +1868,8 @@ static int o2net_accept_one(struct socket *sock, int *more)
>  	if (ret) {
>  		printk(KERN_NOTICE "o2net: Attempt to connect from node '%s' "
>  		       "at %pI4:%d but it already has an open connection\n",
> -		       node->nd_name, &sin.sin_addr.s_addr,
> -		       ntohs(sin.sin_port));
> +		       node->nd_name, &u.sin.sin_addr.s_addr,
> +		       ntohs(u.sin.sin_port));
>  		goto out;
>  	}
>  
> diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
> index 8ddd5a3c7baf..35fc9f7cd0b2 100644
> --- a/fs/smb/server/connection.h
> +++ b/fs/smb/server/connection.h
> @@ -141,7 +141,7 @@ struct ksmbd_transport {
>  
>  #define KSMBD_TCP_RECV_TIMEOUT	(7 * HZ)
>  #define KSMBD_TCP_SEND_TIMEOUT	(5 * HZ)
> -#define KSMBD_TCP_PEER_SOCKADDR(c)	((struct sockaddr *)&((c)->peer_addr))
> +#define KSMBD_TCP_PEER_SOCKADDR(c)	(&((c)->peer_addr))
>  
>  extern struct list_head conn_list;
>  extern struct rw_semaphore conn_list_lock;
> diff --git a/fs/smb/server/mgmt/tree_connect.c b/fs/smb/server/mgmt/tree_connect.c
> index ecfc57508671..06a59fb8d25b 100644
> --- a/fs/smb/server/mgmt/tree_connect.c
> +++ b/fs/smb/server/mgmt/tree_connect.c
> @@ -22,7 +22,7 @@ ksmbd_tree_conn_connect(struct ksmbd_work *work, const char *share_name)
>  	struct ksmbd_tree_connect_response *resp = NULL;
>  	struct ksmbd_share_config *sc;
>  	struct ksmbd_tree_connect *tree_conn = NULL;
> -	struct sockaddr *peer_addr;
> +	struct sockaddr_storage *peer_addr;
>  	struct ksmbd_conn *conn = work->conn;
>  	struct ksmbd_session *sess = work->sess;
>  	int ret;
> diff --git a/fs/smb/server/transport_ipc.c b/fs/smb/server/transport_ipc.c
> index 48cda3350e5a..b279f8f75eeb 100644
> --- a/fs/smb/server/transport_ipc.c
> +++ b/fs/smb/server/transport_ipc.c
> @@ -644,7 +644,7 @@ struct ksmbd_tree_connect_response *
>  ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
>  			       struct ksmbd_share_config *share,
>  			       struct ksmbd_tree_connect *tree_conn,
> -			       struct sockaddr *peer_addr)
> +			       struct sockaddr_storage *peer_addr)
>  {
>  	struct ksmbd_ipc_msg *msg;
>  	struct ksmbd_tree_connect_request *req;
> @@ -671,7 +671,7 @@ ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
>  	strscpy(req->share, share->name, KSMBD_REQ_MAX_SHARE_NAME);
>  	snprintf(req->peer_addr, sizeof(req->peer_addr), "%pIS", peer_addr);
>  
> -	if (peer_addr->sa_family == AF_INET6)
> +	if (peer_addr->ss_family == AF_INET6)
>  		req->flags |= KSMBD_TREE_CONN_FLAG_REQUEST_IPV6;
>  	if (test_session_flag(sess, CIFDS_SESSION_FLAG_SMB2))
>  		req->flags |= KSMBD_TREE_CONN_FLAG_REQUEST_SMB2;
> diff --git a/fs/smb/server/transport_ipc.h b/fs/smb/server/transport_ipc.h
> index d9b6737f8cd0..840b9b1c56f6 100644
> --- a/fs/smb/server/transport_ipc.h
> +++ b/fs/smb/server/transport_ipc.h
> @@ -18,13 +18,13 @@ ksmbd_ipc_login_request_ext(const char *account);
>  struct ksmbd_session;
>  struct ksmbd_share_config;
>  struct ksmbd_tree_connect;
> -struct sockaddr;
> +struct __kernel_sockaddr_storage;
>  
>  struct ksmbd_tree_connect_response *
>  ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
>  			       struct ksmbd_share_config *share,
>  			       struct ksmbd_tree_connect *tree_conn,
> -			       struct sockaddr *peer_addr);
> +			       struct __kernel_sockaddr_storage *peer_addr);
>  int ksmbd_ipc_tree_disconnect_request(unsigned long long session_id,
>  				      unsigned long long connect_id);
>  int ksmbd_ipc_logout_request(const char *account, int flags);
> diff --git a/fs/smb/server/transport_tcp.c b/fs/smb/server/transport_tcp.c
> index 0d9007285e30..7ab1031e554b 100644
> --- a/fs/smb/server/transport_tcp.c
> +++ b/fs/smb/server/transport_tcp.c
> @@ -160,9 +160,9 @@ static struct kvec *get_conn_iovec(struct tcp_transport *t, unsigned int nr_segs
>  	return new_iov;
>  }
>  
> -static unsigned short ksmbd_tcp_get_port(const struct sockaddr *sa)
> +static unsigned short ksmbd_tcp_get_port(const struct sockaddr_storage *sa)
>  {
> -	switch (sa->sa_family) {
> +	switch (sa->ss_family) {
>  	case AF_INET:
>  		return ntohs(((struct sockaddr_in *)sa)->sin_port);
>  	case AF_INET6:
> @@ -182,7 +182,7 @@ static unsigned short ksmbd_tcp_get_port(const struct sockaddr *sa)
>   */
>  static int ksmbd_tcp_new_connection(struct socket *client_sk)
>  {
> -	struct sockaddr *csin;
> +	struct sockaddr_storage *csin;
>  	int rc = 0;
>  	struct tcp_transport *t;
>  	struct task_struct *handler;
> diff --git a/include/linux/net.h b/include/linux/net.h
> index b75bc534c1b3..e31baa3fb360 100644
> --- a/include/linux/net.h
> +++ b/include/linux/net.h
> @@ -175,7 +175,7 @@ struct proto_ops {
>  				      struct socket *newsock,
>  				      struct proto_accept_arg *arg);
>  	int		(*getname)   (struct socket *sock,
> -				      struct sockaddr *addr,
> +				      struct sockaddr_storage *addr,
>  				      int peer);
>  	__poll_t	(*poll)	     (struct file *file, struct socket *sock,
>  				      struct poll_table_struct *wait);
> @@ -353,8 +353,8 @@ int kernel_listen(struct socket *sock, int backlog);
>  int kernel_accept(struct socket *sock, struct socket **newsock, int flags);
>  int kernel_connect(struct socket *sock, struct sockaddr *addr, int addrlen,
>  		   int flags);
> -int kernel_getsockname(struct socket *sock, struct sockaddr *addr);
> -int kernel_getpeername(struct socket *sock, struct sockaddr *addr);
> +int kernel_getsockname(struct socket *sock, struct sockaddr_storage *addr);
> +int kernel_getpeername(struct socket *sock, struct sockaddr_storage *addr);
>  int kernel_sock_shutdown(struct socket *sock, enum sock_shutdown_cmd how);
>  
>  /* Routine returns the IP overhead imposed by a (caller-protected) socket. */
> diff --git a/include/linux/sunrpc/clnt.h b/include/linux/sunrpc/clnt.h
> index 5321585c778f..8f29cede4ff3 100644
> --- a/include/linux/sunrpc/clnt.h
> +++ b/include/linux/sunrpc/clnt.h
> @@ -223,8 +223,8 @@ unsigned int	rpc_num_bc_slots(struct rpc_clnt *);
>  void		rpc_force_rebind(struct rpc_clnt *);
>  size_t		rpc_peeraddr(struct rpc_clnt *, struct sockaddr *, size_t);
>  const char	*rpc_peeraddr2str(struct rpc_clnt *, enum rpc_display_format_t);
> -int		rpc_localaddr(struct rpc_clnt *, struct sockaddr *, size_t);
> -
> +int		rpc_localaddr(struct rpc_clnt *clnt, struct sockaddr_storage *buf,
> +			      size_t buflen);
>  int 		rpc_clnt_iterate_for_each_xprt(struct rpc_clnt *clnt,
>  			int (*fn)(struct rpc_clnt *, struct rpc_xprt *, void *),
>  			void *data);
> diff --git a/include/net/inet_common.h b/include/net/inet_common.h
> index c17a6585d0b0..2bc95e0171e7 100644
> --- a/include/net/inet_common.h
> +++ b/include/net/inet_common.h
> @@ -54,7 +54,7 @@ int inet_bind_sk(struct sock *sk, struct sockaddr *uaddr, int addr_len);
>  #define BIND_NO_CAP_NET_BIND_SERVICE	(1 << 3)
>  int __inet_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len,
>  		u32 flags);
> -int inet_getname(struct socket *sock, struct sockaddr *uaddr,
> +int inet_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  		 int peer);
>  int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
>  int inet_ctl_sock_create(struct sock **sk, unsigned short family,
> diff --git a/include/net/ipv6.h b/include/net/ipv6.h
> index 248bfb26e2af..e0ee07a8486e 100644
> --- a/include/net/ipv6.h
> +++ b/include/net/ipv6.h
> @@ -1214,7 +1214,7 @@ void inet6_sock_destruct(struct sock *sk);
>  int inet6_release(struct socket *sock);
>  int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len);
>  int inet6_bind_sk(struct sock *sk, struct sockaddr *uaddr, int addr_len);
> -int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
> +int inet6_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  		  int peer);
>  int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
>  int inet6_compat_ioctl(struct socket *sock, unsigned int cmd,
> diff --git a/include/net/sock.h b/include/net/sock.h
> index 7464e9f9f47c..647edced2a51 100644
> --- a/include/net/sock.h
> +++ b/include/net/sock.h
> @@ -1837,7 +1837,8 @@ int sock_no_bind(struct socket *, struct sockaddr *, int);
>  int sock_no_connect(struct socket *, struct sockaddr *, int, int);
>  int sock_no_socketpair(struct socket *, struct socket *);
>  int sock_no_accept(struct socket *, struct socket *, struct proto_accept_arg *);
> -int sock_no_getname(struct socket *, struct sockaddr *, int);
> +int sock_no_getname(struct socket *sock, struct sockaddr_storage *saddr,
> +		    int peer);
>  int sock_no_ioctl(struct socket *, unsigned int, unsigned long);
>  int sock_no_listen(struct socket *, int);
>  int sock_no_shutdown(struct socket *, int);
> diff --git a/net/appletalk/ddp.c b/net/appletalk/ddp.c
> index b068651984fe..b0a5137e9dce 100644
> --- a/net/appletalk/ddp.c
> +++ b/net/appletalk/ddp.c
> @@ -1258,7 +1258,7 @@ static int atalk_connect(struct socket *sock, struct sockaddr *uaddr,
>   * Find the name of an AppleTalk socket. Just copy the right
>   * fields into the sockaddr.
>   */
> -static int atalk_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int atalk_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			 int peer)
>  {
>  	struct sockaddr_at sat;
> diff --git a/net/atm/pvc.c b/net/atm/pvc.c
> index 66d9a9bd5896..897b82de7a5b 100644
> --- a/net/atm/pvc.c
> +++ b/net/atm/pvc.c
> @@ -86,7 +86,7 @@ static int pvc_getsockopt(struct socket *sock, int level, int optname,
>  	return error;
>  }
>  
> -static int pvc_getname(struct socket *sock, struct sockaddr *sockaddr,
> +static int pvc_getname(struct socket *sock, struct sockaddr_storage *sockaddr,
>  		       int peer)
>  {
>  	struct sockaddr_atmpvc *addr;
> diff --git a/net/atm/svc.c b/net/atm/svc.c
> index f8137ae693b0..b02f5833cc9a 100644
> --- a/net/atm/svc.c
> +++ b/net/atm/svc.c
> @@ -423,7 +423,7 @@ static int svc_accept(struct socket *sock, struct socket *newsock,
>  	return error;
>  }
>  
> -static int svc_getname(struct socket *sock, struct sockaddr *sockaddr,
> +static int svc_getname(struct socket *sock, struct sockaddr_storage *sockaddr,
>  		       int peer)
>  {
>  	struct sockaddr_atmsvc *addr;
> diff --git a/net/ax25/af_ax25.c b/net/ax25/af_ax25.c
> index d6f9fae06a9d..82edd9d8a8f6 100644
> --- a/net/ax25/af_ax25.c
> +++ b/net/ax25/af_ax25.c
> @@ -1447,8 +1447,8 @@ static int ax25_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int ax25_getname(struct socket *sock, struct sockaddr *uaddr,
> -	int peer)
> +static int ax25_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +			int peer)
>  {
>  	struct full_sockaddr_ax25 *fsa = (struct full_sockaddr_ax25 *)uaddr;
>  	struct sock *sk = sock->sk;
> diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
> index 2272e1849ebd..3fe844460fc4 100644
> --- a/net/bluetooth/hci_sock.c
> +++ b/net/bluetooth/hci_sock.c
> @@ -1478,7 +1478,7 @@ static int hci_sock_bind(struct socket *sock, struct sockaddr *addr,
>  	return err;
>  }
>  
> -static int hci_sock_getname(struct socket *sock, struct sockaddr *addr,
> +static int hci_sock_getname(struct socket *sock, struct sockaddr_storage *addr,
>  			    int peer)
>  {
>  	struct sockaddr_hci *haddr = (struct sockaddr_hci *)addr;
> diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c
> index 1b40fd2b2f02..37696247b9a8 100644
> --- a/net/bluetooth/iso.c
> +++ b/net/bluetooth/iso.c
> @@ -1273,15 +1273,15 @@ static int iso_sock_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int iso_sock_getname(struct socket *sock, struct sockaddr *addr,
> -			    int peer)
> +static int iso_sock_getname(struct socket *sock,
> +			    struct sockaddr_storage *addr, int peer)
>  {
>  	struct sockaddr_iso *sa = (struct sockaddr_iso *)addr;
>  	struct sock *sk = sock->sk;
>  
>  	BT_DBG("sock %p, sk %p", sock, sk);
>  
> -	addr->sa_family = AF_BLUETOOTH;
> +	sa->iso_family = AF_BLUETOOTH;
>  
>  	if (peer) {
>  		bacpy(&sa->iso_bdaddr, &iso_pi(sk)->dst);
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index 18e89e764f3b..799b2991ab17 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -382,8 +382,8 @@ static int l2cap_sock_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr,
> -			      int peer)
> +static int l2cap_sock_getname(struct socket *sock,
> +			      struct sockaddr_storage *addr, int peer)
>  {
>  	struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr;
>  	struct sock *sk = sock->sk;
> @@ -397,7 +397,7 @@ static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr,
>  		return -ENOTCONN;
>  
>  	memset(la, 0, sizeof(struct sockaddr_l2));
> -	addr->sa_family = AF_BLUETOOTH;
> +	la->l2_family = AF_BLUETOOTH;
>  
>  	la->l2_psm = chan->psm;
>  
> diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c
> index 40766f8119ed..167a95f446da 100644
> --- a/net/bluetooth/rfcomm/sock.c
> +++ b/net/bluetooth/rfcomm/sock.c
> @@ -529,7 +529,8 @@ static int rfcomm_sock_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int rfcomm_sock_getname(struct socket *sock, struct sockaddr *addr, int peer)
> +static int rfcomm_sock_getname(struct socket *sock, struct sockaddr_storage *addr,
> +			       int peer)
>  {
>  	struct sockaddr_rc *sa = (struct sockaddr_rc *) addr;
>  	struct sock *sk = sock->sk;
> diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
> index 78f7bca24487..a828bc2990d0 100644
> --- a/net/bluetooth/sco.c
> +++ b/net/bluetooth/sco.c
> @@ -750,15 +750,15 @@ static int sco_sock_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int sco_sock_getname(struct socket *sock, struct sockaddr *addr,
> -			    int peer)
> +static int sco_sock_getname(struct socket *sock,
> +			    struct sockaddr_storage *addr, int peer)
>  {
>  	struct sockaddr_sco *sa = (struct sockaddr_sco *) addr;
>  	struct sock *sk = sock->sk;
>  
>  	BT_DBG("sock %p, sk %p", sock, sk);
>  
> -	addr->sa_family = AF_BLUETOOTH;
> +	sa->sco_family = AF_BLUETOOTH;
>  
>  	if (peer)
>  		bacpy(&sa->sco_bdaddr, &sco_pi(sk)->dst);
> diff --git a/net/can/isotp.c b/net/can/isotp.c
> index 16046931542a..5afb88885548 100644
> --- a/net/can/isotp.c
> +++ b/net/can/isotp.c
> @@ -1352,7 +1352,8 @@ static int isotp_bind(struct socket *sock, struct sockaddr *uaddr, int len)
>  	return err;
>  }
>  
> -static int isotp_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
> +static int isotp_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +			 int peer)
>  {
>  	struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
>  	struct sock *sk = sock->sk;
> diff --git a/net/can/j1939/socket.c b/net/can/j1939/socket.c
> index 305dd72c844c..66ea829811ab 100644
> --- a/net/can/j1939/socket.c
> +++ b/net/can/j1939/socket.c
> @@ -598,7 +598,7 @@ static void j1939_sk_sock2sockaddr_can(struct sockaddr_can *addr,
>  	}
>  }
>  
> -static int j1939_sk_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int j1939_sk_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			    int peer)
>  {
>  	struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
> diff --git a/net/can/raw.c b/net/can/raw.c
> index 255c0a8f39d6..551aec0e321e 100644
> --- a/net/can/raw.c
> +++ b/net/can/raw.c
> @@ -530,7 +530,7 @@ static int raw_bind(struct socket *sock, struct sockaddr *uaddr, int len)
>  	return err;
>  }
>  
> -static int raw_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int raw_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  		       int peer)
>  {
>  	struct sockaddr_can *addr = (struct sockaddr_can *)uaddr;
> diff --git a/net/core/sock.c b/net/core/sock.c
> index 74729d20cd00..fd6d42512530 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1909,7 +1909,7 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
>  	{
>  		struct sockaddr_storage address;
>  
> -		lv = READ_ONCE(sock->ops)->getname(sock, (struct sockaddr *)&address, 2);
> +		lv = READ_ONCE(sock->ops)->getname(sock, &address, 2);
>  		if (lv < 0)
>  			return -ENOTCONN;
>  		if (lv < len)
> @@ -3348,7 +3348,7 @@ int sock_no_accept(struct socket *sock, struct socket *newsock,
>  }
>  EXPORT_SYMBOL(sock_no_accept);
>  
> -int sock_no_getname(struct socket *sock, struct sockaddr *saddr,
> +int sock_no_getname(struct socket *sock, struct sockaddr_storage *saddr,
>  		    int peer)
>  {
>  	return -EOPNOTSUPP;
> diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
> index 8095e82de808..1ebe2ebe9f22 100644
> --- a/net/ipv4/af_inet.c
> +++ b/net/ipv4/af_inet.c
> @@ -792,7 +792,7 @@ EXPORT_SYMBOL(inet_accept);
>  /*
>   *	This does both peername and sockname.
>   */
> -int inet_getname(struct socket *sock, struct sockaddr *uaddr,
> +int inet_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  		 int peer)
>  {
>  	struct sock *sk		= sock->sk;
> diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
> index f60ec8b0f8ea..78d29dbeda1c 100644
> --- a/net/ipv6/af_inet6.c
> +++ b/net/ipv6/af_inet6.c
> @@ -518,7 +518,7 @@ EXPORT_SYMBOL_GPL(inet6_cleanup_sock);
>  /*
>   *	This does both peername and sockname.
>   */
> -int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
> +int inet6_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  		  int peer)
>  {
>  	struct sockaddr_in6 *sin = (struct sockaddr_in6 *)uaddr;
> diff --git a/net/iucv/af_iucv.c b/net/iucv/af_iucv.c
> index 7929df08d4e0..2612382e1a48 100644
> --- a/net/iucv/af_iucv.c
> +++ b/net/iucv/af_iucv.c
> @@ -848,14 +848,14 @@ static int iucv_sock_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int iucv_sock_getname(struct socket *sock, struct sockaddr *addr,
> -			     int peer)
> +static int iucv_sock_getname(struct socket *sock,
> +			     struct sockaddr_storage *addr, int peer)
>  {
>  	DECLARE_SOCKADDR(struct sockaddr_iucv *, siucv, addr);
>  	struct sock *sk = sock->sk;
>  	struct iucv_sock *iucv = iucv_sk(sk);
>  
> -	addr->sa_family = AF_IUCV;
> +	siucv->sa_family = AF_IUCV;
>  
>  	if (peer) {
>  		memcpy(siucv->siucv_user_id, iucv->dst_user_id, 8);
> diff --git a/net/l2tp/l2tp_ip.c b/net/l2tp/l2tp_ip.c
> index 4bc24fddfd52..ed92eabb8552 100644
> --- a/net/l2tp/l2tp_ip.c
> +++ b/net/l2tp/l2tp_ip.c
> @@ -373,7 +373,7 @@ static int l2tp_ip_disconnect(struct sock *sk, int flags)
>  	return __udp_disconnect(sk, flags);
>  }
>  
> -static int l2tp_ip_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int l2tp_ip_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			   int peer)
>  {
>  	struct sock *sk		= sock->sk;
> diff --git a/net/l2tp/l2tp_ip6.c b/net/l2tp/l2tp_ip6.c
> index f4c1da070826..59a5e74b2561 100644
> --- a/net/l2tp/l2tp_ip6.c
> +++ b/net/l2tp/l2tp_ip6.c
> @@ -443,7 +443,7 @@ static int l2tp_ip6_disconnect(struct sock *sk, int flags)
>  	return __udp_disconnect(sk, flags);
>  }
>  
> -static int l2tp_ip6_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int l2tp_ip6_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			    int peer)
>  {
>  	struct sockaddr_l2tpip6 *lsa = (struct sockaddr_l2tpip6 *)uaddr;
> diff --git a/net/l2tp/l2tp_ppp.c b/net/l2tp/l2tp_ppp.c
> index 53baf2dd5d5d..ae1536ed5a5b 100644
> --- a/net/l2tp/l2tp_ppp.c
> +++ b/net/l2tp/l2tp_ppp.c
> @@ -886,7 +886,7 @@ static int pppol2tp_session_create(struct net *net, struct l2tp_tunnel *tunnel,
>  
>  /* getname() support.
>   */
> -static int pppol2tp_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int pppol2tp_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			    int peer)
>  {
>  	int len = 0;
> diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c
> index 0259cde394ba..12c0b14e1ef6 100644
> --- a/net/llc/af_llc.c
> +++ b/net/llc/af_llc.c
> @@ -1023,7 +1023,7 @@ static int llc_ui_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
>   *
>   *	Return the address information of a socket.
>   */
> -static int llc_ui_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int llc_ui_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			  int peer)
>  {
>  	struct sockaddr_llc sllc;
> diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c
> index f4e7b5e4bb59..64ec9293136f 100644
> --- a/net/netlink/af_netlink.c
> +++ b/net/netlink/af_netlink.c
> @@ -1113,8 +1113,8 @@ static int netlink_connect(struct socket *sock, struct sockaddr *addr,
>  	return err;
>  }
>  
> -static int netlink_getname(struct socket *sock, struct sockaddr *addr,
> -			   int peer)
> +static int netlink_getname(struct socket *sock,
> +			   struct sockaddr_storage *addr, int peer)
>  {
>  	struct sock *sk = sock->sk;
>  	struct netlink_sock *nlk = nlk_sk(sk);
> diff --git a/net/netrom/af_netrom.c b/net/netrom/af_netrom.c
> index 6ee148f0e6d0..f845c0a56b75 100644
> --- a/net/netrom/af_netrom.c
> +++ b/net/netrom/af_netrom.c
> @@ -835,8 +835,8 @@ static int nr_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int nr_getname(struct socket *sock, struct sockaddr *uaddr,
> -	int peer)
> +static int nr_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +		      int peer)
>  {
>  	struct full_sockaddr_ax25 *sax = (struct full_sockaddr_ax25 *)uaddr;
>  	struct sock *sk = sock->sk;
> diff --git a/net/nfc/llcp_sock.c b/net/nfc/llcp_sock.c
> index 57a2f97004e1..1ba19e542320 100644
> --- a/net/nfc/llcp_sock.c
> +++ b/net/nfc/llcp_sock.c
> @@ -500,8 +500,8 @@ static int llcp_sock_accept(struct socket *sock, struct socket *newsock,
>  	return ret;
>  }
>  
> -static int llcp_sock_getname(struct socket *sock, struct sockaddr *uaddr,
> -			     int peer)
> +static int llcp_sock_getname(struct socket *sock,
> +			     struct sockaddr_storage *uaddr, int peer)
>  {
>  	struct sock *sk = sock->sk;
>  	struct nfc_llcp_sock *llcp_sock = nfc_llcp_sock(sk);
> diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
> index 886c0dd47b66..3661476920af 100644
> --- a/net/packet/af_packet.c
> +++ b/net/packet/af_packet.c
> @@ -3640,27 +3640,28 @@ static int packet_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
>  	return err;
>  }
>  
> -static int packet_getname_spkt(struct socket *sock, struct sockaddr *uaddr,
> -			       int peer)
> +static int packet_getname_spkt(struct socket *sock,
> +			       struct sockaddr_storage *uaddr, int peer)
>  {
> +	struct sockaddr *addr = (struct sockaddr *)uaddr;
>  	struct net_device *dev;
>  	struct sock *sk	= sock->sk;
>  
>  	if (peer)
>  		return -EOPNOTSUPP;
>  
> -	uaddr->sa_family = AF_PACKET;
> -	memset(uaddr->sa_data, 0, sizeof(uaddr->sa_data_min));
> +	addr->sa_family = AF_PACKET;
> +	memset(addr->sa_data, 0, sizeof(addr->sa_data_min));
>  	rcu_read_lock();
>  	dev = dev_get_by_index_rcu(sock_net(sk), READ_ONCE(pkt_sk(sk)->ifindex));
>  	if (dev)
> -		strscpy(uaddr->sa_data, dev->name, sizeof(uaddr->sa_data_min));
> +		strscpy(addr->sa_data, dev->name, sizeof(addr->sa_data_min));
>  	rcu_read_unlock();
>  
> -	return sizeof(*uaddr);
> +	return sizeof(*addr);
>  }
>  
> -static int packet_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int packet_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			  int peer)
>  {
>  	struct net_device *dev;
> diff --git a/net/phonet/socket.c b/net/phonet/socket.c
> index 5ce0b3ee5def..711c70cc110a 100644
> --- a/net/phonet/socket.c
> +++ b/net/phonet/socket.c
> @@ -311,17 +311,17 @@ static int pn_socket_accept(struct socket *sock, struct socket *newsock,
>  	return 0;
>  }
>  
> -static int pn_socket_getname(struct socket *sock, struct sockaddr *addr,
> -				int peer)
> +static int pn_socket_getname(struct socket *sock,
> +			     struct sockaddr_storage *uaddr, int peer)
>  {
> +	struct sockaddr_pn *addr = (struct sockaddr_pn *)uaddr;
>  	struct sock *sk = sock->sk;
>  	struct pn_sock *pn = pn_sk(sk);
>  
>  	memset(addr, 0, sizeof(struct sockaddr_pn));
> -	addr->sa_family = AF_PHONET;
> +	addr->spn_family = AF_PHONET;
>  	if (!peer) /* Race with bind() here is userland's problem. */
> -		pn_sockaddr_set_object((struct sockaddr_pn *)addr,
> -					pn->sobject);
> +		pn_sockaddr_set_object(addr, pn->sobject);
>  
>  	return sizeof(struct sockaddr_pn);
>  }
> diff --git a/net/qrtr/af_qrtr.c b/net/qrtr/af_qrtr.c
> index 00c51cf693f3..836c9a4a2119 100644
> --- a/net/qrtr/af_qrtr.c
> +++ b/net/qrtr/af_qrtr.c
> @@ -1115,7 +1115,7 @@ static int qrtr_connect(struct socket *sock, struct sockaddr *saddr,
>  	return 0;
>  }
>  
> -static int qrtr_getname(struct socket *sock, struct sockaddr *saddr,
> +static int qrtr_getname(struct socket *sock, struct sockaddr_storage *saddr,
>  			int peer)
>  {
>  	struct qrtr_sock *ipc = qrtr_sk(sock->sk);
> diff --git a/net/qrtr/ns.c b/net/qrtr/ns.c
> index 3de9350cbf30..0409983eb9fb 100644
> --- a/net/qrtr/ns.c
> +++ b/net/qrtr/ns.c
> @@ -697,7 +697,7 @@ int qrtr_ns_init(void)
>  	if (ret < 0)
>  		return ret;
>  
> -	ret = kernel_getsockname(qrtr_ns.sock, (struct sockaddr *)&sq);
> +	ret = kernel_getsockname(qrtr_ns.sock, (struct sockaddr_storage *)&sq);
>  	if (ret < 0) {
>  		pr_err("failed to get socket name\n");
>  		goto err_sock;
> diff --git a/net/rds/af_rds.c b/net/rds/af_rds.c
> index 8435a20968ef..6d0cef028454 100644
> --- a/net/rds/af_rds.c
> +++ b/net/rds/af_rds.c
> @@ -111,7 +111,7 @@ void rds_wake_sk_sleep(struct rds_sock *rs)
>  	read_unlock_irqrestore(&rs->rs_recv_lock, flags);
>  }
>  
> -static int rds_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int rds_getname(struct socket *sock, struct sockaddr_storage *uaddr,
I missed this small rds change here earlier.  This change looks fine to me.  Thanks Kees!  Sorry for the delay!
Acked-by: Allison Henderson <allison.henderson@oracle.com>

Allison
>  		       int peer)
>  {
>  	struct rds_sock *rs = rds_sk_to_rs(sock->sk);
> diff --git a/net/rose/af_rose.c b/net/rose/af_rose.c
> index 59050caab65c..0260a5862cf7 100644
> --- a/net/rose/af_rose.c
> +++ b/net/rose/af_rose.c
> @@ -984,8 +984,8 @@ static int rose_accept(struct socket *sock, struct socket *newsock,
>  	return err;
>  }
>  
> -static int rose_getname(struct socket *sock, struct sockaddr *uaddr,
> -	int peer)
> +static int rose_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +			int peer)
>  {
>  	struct full_sockaddr_rose *srose = (struct full_sockaddr_rose *)uaddr;
>  	struct sock *sk = sock->sk;
> diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c
> index a9ed2ccab1bd..2d8decaae609 100644
> --- a/net/sctp/ipv6.c
> +++ b/net/sctp/ipv6.c
> @@ -1065,7 +1065,7 @@ static int sctp_inet6_supported_addrs(const struct sctp_sock *opt,
>  }
>  
>  /* Handle SCTP_I_WANT_MAPPED_V4_ADDR for getpeername() and getsockname() */
> -static int sctp_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int sctp_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			int peer)
>  {
>  	int rc;
> diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
> index 9e6c69d18581..5d44d2392268 100644
> --- a/net/smc/af_smc.c
> +++ b/net/smc/af_smc.c
> @@ -2741,7 +2741,7 @@ int smc_accept(struct socket *sock, struct socket *new_sock,
>  	return rc;
>  }
>  
> -int smc_getname(struct socket *sock, struct sockaddr *addr,
> +int smc_getname(struct socket *sock, struct sockaddr_storage *addr,
>  		int peer)
>  {
>  	struct smc_sock *smc;
> diff --git a/net/smc/smc.h b/net/smc/smc.h
> index 78ae10d06ed2..24260f4e6830 100644
> --- a/net/smc/smc.h
> +++ b/net/smc/smc.h
> @@ -48,7 +48,7 @@ int smc_connect(struct socket *sock, struct sockaddr *addr,
>  		int alen, int flags);
>  int smc_accept(struct socket *sock, struct socket *new_sock,
>  	       struct proto_accept_arg *arg);
> -int smc_getname(struct socket *sock, struct sockaddr *addr,
> +int smc_getname(struct socket *sock, struct sockaddr_storage *addr,
>  		int peer);
>  __poll_t smc_poll(struct file *file, struct socket *sock,
>  		  poll_table *wait);
> diff --git a/net/smc/smc_clc.c b/net/smc/smc_clc.c
> index 33fa787c28eb..6eeb5decc51e 100644
> --- a/net/smc/smc_clc.c
> +++ b/net/smc/smc_clc.c
> @@ -571,7 +571,7 @@ static int smc_clc_prfx_set(struct socket *clcsock,
>  		goto out_rel;
>  	}
>  	/* get address to which the internal TCP socket is bound */
> -	if (kernel_getsockname(clcsock, (struct sockaddr *)&addrs) < 0)
> +	if (kernel_getsockname(clcsock, &addrs) < 0)
>  		goto out_rel;
>  	/* analyze IP specific data of net_device belonging to TCP socket */
>  	addr6 = (struct sockaddr_in6 *)&addrs;
> diff --git a/net/socket.c b/net/socket.c
> index 9a117248f18f..b10f2b3f4054 100644
> --- a/net/socket.c
> +++ b/net/socket.c
> @@ -1943,7 +1943,7 @@ struct file *do_accept(struct file *file, struct proto_accept_arg *arg,
>  		goto out_fd;
>  
>  	if (upeer_sockaddr) {
> -		len = ops->getname(newsock, (struct sockaddr *)&address, 2);
> +		len = ops->getname(newsock, &address, 2);
>  		if (len < 0) {
>  			err = -ECONNABORTED;
>  			goto out_fd;
> @@ -2103,7 +2103,7 @@ int __sys_getsockname(int fd, struct sockaddr __user *usockaddr,
>  	if (err)
>  		return err;
>  
> -	err = READ_ONCE(sock->ops)->getname(sock, (struct sockaddr *)&address, 0);
> +	err = READ_ONCE(sock->ops)->getname(sock, &address, 0);
>  	if (err < 0)
>  		return err;
>  
> @@ -2140,7 +2140,7 @@ int __sys_getpeername(int fd, struct sockaddr __user *usockaddr,
>  	if (err)
>  		return err;
>  
> -	err = READ_ONCE(sock->ops)->getname(sock, (struct sockaddr *)&address, 1);
> +	err = READ_ONCE(sock->ops)->getname(sock, &address, 1);
>  	if (err < 0)
>  		return err;
>  
> @@ -3636,7 +3636,7 @@ EXPORT_SYMBOL(kernel_connect);
>   *	Returns the length of the address in bytes or an error code.
>   */
>  
> -int kernel_getsockname(struct socket *sock, struct sockaddr *addr)
> +int kernel_getsockname(struct socket *sock, struct sockaddr_storage *addr)
>  {
>  	return READ_ONCE(sock->ops)->getname(sock, addr, 0);
>  }
> @@ -3651,7 +3651,7 @@ EXPORT_SYMBOL(kernel_getsockname);
>   *	Returns the length of the address in bytes or an error code.
>   */
>  
> -int kernel_getpeername(struct socket *sock, struct sockaddr *addr)
> +int kernel_getpeername(struct socket *sock, struct sockaddr_storage *addr)
>  {
>  	return READ_ONCE(sock->ops)->getname(sock, addr, 1);
>  }
> diff --git a/net/sunrpc/clnt.c b/net/sunrpc/clnt.c
> index 0090162ee8c3..8af253f5ad08 100644
> --- a/net/sunrpc/clnt.c
> +++ b/net/sunrpc/clnt.c
> @@ -1445,7 +1445,7 @@ static const struct sockaddr_in6 rpc_in6addr_loopback = {
>   * negative errno is returned.
>   */
>  static int rpc_sockname(struct net *net, struct sockaddr *sap, size_t salen,
> -			struct sockaddr *buf)
> +			struct sockaddr_storage *buf)
>  {
>  	struct socket *sock;
>  	int err;
> @@ -1490,7 +1490,7 @@ static int rpc_sockname(struct net *net, struct sockaddr *sap, size_t salen,
>  	}
>  
>  	err = 0;
> -	if (buf->sa_family == AF_INET6) {
> +	if (buf->ss_family == AF_INET6) {
>  		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)buf;
>  		sin6->sin6_scope_id = 0;
>  	}
> @@ -1510,7 +1510,7 @@ static int rpc_sockname(struct net *net, struct sockaddr *sap, size_t salen,
>   * Returns zero and fills in "buf" if successful; otherwise, a
>   * negative errno is returned.
>   */
> -static int rpc_anyaddr(int family, struct sockaddr *buf, size_t buflen)
> +static int rpc_anyaddr(int family, struct sockaddr_storage *buf, size_t buflen)
>  {
>  	switch (family) {
>  	case AF_INET:
> @@ -1550,7 +1550,8 @@ static int rpc_anyaddr(int family, struct sockaddr *buf, size_t buflen)
>   * succession may give different results, depending on how local
>   * networking configuration changes over time.
>   */
> -int rpc_localaddr(struct rpc_clnt *clnt, struct sockaddr *buf, size_t buflen)
> +int rpc_localaddr(struct rpc_clnt *clnt, struct sockaddr_storage *buf,
> +		  size_t buflen)
>  {
>  	struct sockaddr_storage address;
>  	struct sockaddr *sap = (struct sockaddr *)&address;
> diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
> index 95397677673b..b062fadb2e6b 100644
> --- a/net/sunrpc/svcsock.c
> +++ b/net/sunrpc/svcsock.c
> @@ -903,7 +903,7 @@ static struct svc_xprt *svc_tcp_accept(struct svc_xprt *xprt)
>  
>  	set_bit(XPT_CONN, &svsk->sk_xprt.xpt_flags);
>  
> -	err = kernel_getpeername(newsock, sin);
> +	err = kernel_getpeername(newsock, &addr);
>  	if (err < 0) {
>  		trace_svcsock_getpeername_err(xprt, serv->sv_name, err);
>  		goto failed;		/* aborted connection or whatever */
> @@ -925,7 +925,7 @@ static struct svc_xprt *svc_tcp_accept(struct svc_xprt *xprt)
>  	if (IS_ERR(newsvsk))
>  		goto failed;
>  	svc_xprt_set_remote(&newsvsk->sk_xprt, sin, slen);
> -	err = kernel_getsockname(newsock, sin);
> +	err = kernel_getsockname(newsock, &addr);
>  	slen = err;
>  	if (unlikely(err < 0))
>  		slen = offsetof(struct sockaddr, sa_data);
> @@ -1478,7 +1478,7 @@ int svc_addsock(struct svc_serv *serv, struct net *net, const int fd,
>  		err = PTR_ERR(svsk);
>  		goto out;
>  	}
> -	salen = kernel_getsockname(svsk->sk_sock, sin);
> +	salen = kernel_getsockname(svsk->sk_sock, &addr);
>  	if (salen >= 0)
>  		svc_xprt_set_local(&svsk->sk_xprt, sin, salen);
>  	svsk->sk_xprt.xpt_cred = get_cred(cred);
> @@ -1545,7 +1545,7 @@ static struct svc_xprt *svc_create_socket(struct svc_serv *serv,
>  	if (error < 0)
>  		goto bummer;
>  
> -	error = kernel_getsockname(sock, newsin);
> +	error = kernel_getsockname(sock, &addr);
>  	if (error < 0)
>  		goto bummer;
>  	newlen = error;
> diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
> index c60936d8cef7..735c3c1d2bdf 100644
> --- a/net/sunrpc/xprtsock.c
> +++ b/net/sunrpc/xprtsock.c
> @@ -1710,7 +1710,7 @@ static unsigned short xs_sock_getport(struct socket *sock)
>  	struct sockaddr_storage buf;
>  	unsigned short port = 0;
>  
> -	if (kernel_getsockname(sock, (struct sockaddr *)&buf) < 0)
> +	if (kernel_getsockname(sock, &buf) < 0)
>  		goto out;
>  	switch (buf.ss_family) {
>  	case AF_INET6:
> @@ -1779,7 +1779,7 @@ static int xs_sock_srcaddr(struct rpc_xprt *xprt, char *buf, size_t buflen)
>  
>  	mutex_lock(&sock->recv_mutex);
>  	if (sock->sock) {
> -		ret = kernel_getsockname(sock->sock, &saddr.sa);
> +		ret = kernel_getsockname(sock->sock, &saddr.st);
>  		if (ret >= 0)
>  			ret = snprintf(buf, buflen, "%pISc", &saddr.sa);
>  	}
> diff --git a/net/tipc/socket.c b/net/tipc/socket.c
> index 65dcbb54f55d..56f609c7f2a6 100644
> --- a/net/tipc/socket.c
> +++ b/net/tipc/socket.c
> @@ -741,7 +741,7 @@ static int tipc_bind(struct socket *sock, struct sockaddr *skaddr, int alen)
>   *       accesses socket information that is unchanging (or which changes in
>   *       a completely predictable manner).
>   */
> -static int tipc_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int tipc_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  			int peer)
>  {
>  	struct sockaddr_tipc *addr = (struct sockaddr_tipc *)uaddr;
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 001ccc55ef0f..0f71ed2f35e7 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -813,7 +813,7 @@ static int unix_stream_connect(struct socket *, struct sockaddr *,
>  			       int addr_len, int flags);
>  static int unix_socketpair(struct socket *, struct socket *);
>  static int unix_accept(struct socket *, struct socket *, struct proto_accept_arg *arg);
> -static int unix_getname(struct socket *, struct sockaddr *, int);
> +static int unix_getname(struct socket *, struct sockaddr_storage *, int);
>  static __poll_t unix_poll(struct file *, struct socket *, poll_table *);
>  static __poll_t unix_dgram_poll(struct file *, struct socket *,
>  				    poll_table *);
> @@ -1789,7 +1789,8 @@ static int unix_accept(struct socket *sock, struct socket *newsock,
>  }
>  
>  
> -static int unix_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
> +static int unix_getname(struct socket *sock, struct sockaddr_storage *uaddr,
> +			int peer)
>  {
>  	struct sock *sk = sock->sk;
>  	struct unix_address *addr;
> @@ -1817,10 +1818,10 @@ static int unix_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
>  		memcpy(sunaddr, addr->name, addr->len);
>  
>  		if (peer)
> -			BPF_CGROUP_RUN_SA_PROG(sk, uaddr, &err,
> +			BPF_CGROUP_RUN_SA_PROG(sk, (struct sockaddr *)uaddr, &err,
>  					       CGROUP_UNIX_GETPEERNAME);
>  		else
> -			BPF_CGROUP_RUN_SA_PROG(sk, uaddr, &err,
> +			BPF_CGROUP_RUN_SA_PROG(sk, (struct sockaddr *)uaddr, &err,
>  					       CGROUP_UNIX_GETSOCKNAME);
>  	}
>  	sock_put(sk);
> diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c
> index 5cf8109f672a..6b51ee9cb8d3 100644
> --- a/net/vmw_vsock/af_vsock.c
> +++ b/net/vmw_vsock/af_vsock.c
> @@ -943,7 +943,7 @@ vsock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
>  }
>  
>  static int vsock_getname(struct socket *sock,
> -			 struct sockaddr *addr, int peer)
> +			 struct sockaddr_storage *addr, int peer)
>  {
>  	int err;
>  	struct sock *sk;
> diff --git a/net/x25/af_x25.c b/net/x25/af_x25.c
> index 8dda4178497c..f97876d30935 100644
> --- a/net/x25/af_x25.c
> +++ b/net/x25/af_x25.c
> @@ -913,7 +913,7 @@ static int x25_accept(struct socket *sock, struct socket *newsock,
>  	return rc;
>  }
>  
> -static int x25_getname(struct socket *sock, struct sockaddr *uaddr,
> +static int x25_getname(struct socket *sock, struct sockaddr_storage *uaddr,
>  		       int peer)
>  {
>  	struct sockaddr_x25 *sx25 = (struct sockaddr_x25 *)uaddr;
> diff --git a/security/tomoyo/network.c b/security/tomoyo/network.c
> index 8dc61335f65e..450fd7a37ca4 100644
> --- a/security/tomoyo/network.c
> +++ b/security/tomoyo/network.c
> @@ -658,8 +658,7 @@ int tomoyo_socket_listen_permission(struct socket *sock)
>  	if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET))
>  		return 0;
>  	{
> -		const int error = sock->ops->getname(sock, (struct sockaddr *)
> -						     &addr, 0);
> +		const int error = sock->ops->getname(sock, &addr, 0);
>  
>  		if (error < 0)
>  			return error;


^ permalink raw reply

* Re: [PATCH] can: ctucanfd: handle skb allocation failure
From: Pavel Pisa @ 2025-01-14 16:06 UTC (permalink / raw)
  To: Fedor Pchelkin, Marc Kleine-Budde
  Cc: Ondrej Ille, Vincent Mailhol, Martin Jerabek, linux-can,
	linux-kernel, lvc-project, stable
In-Reply-To: <20250114152138.139580-1-pchelkin@ispras.ru>

Hello Fedor,

thanks for spotting the problem.

On Tuesday 14 of January 2025 16:21:38 Fedor Pchelkin wrote:
> If skb allocation fails, the pointer to struct can_frame is NULL. This
> is actually handled everywhere inside ctucan_err_interrupt() except for
> the only place.
>
> Add the missed NULL check.
>
> Found by Linux Verification Center (linuxtesting.org) with SVACE static
> analysis tool.
>
> Fixes: 2dcb8e8782d8 ("can: ctucanfd: add support for CTU CAN FD open-source
> IP core - bus independent part.") Cc: stable@vger.kernel.org
> Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>

Acked-by: Pavel Pisa <pisa@cmp.felk.cvut.cz>

> ---
>  drivers/net/can/ctucanfd/ctucanfd_base.c | 10 ++++++----
>  1 file changed, 6 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/net/can/ctucanfd/ctucanfd_base.c
> b/drivers/net/can/ctucanfd/ctucanfd_base.c index 64c349fd4600..f65c1a1e05cc
> 100644
> --- a/drivers/net/can/ctucanfd/ctucanfd_base.c
> +++ b/drivers/net/can/ctucanfd/ctucanfd_base.c
> @@ -867,10 +867,12 @@ static void ctucan_err_interrupt(struct net_device
> *ndev, u32 isr) }
>  			break;
>  		case CAN_STATE_ERROR_ACTIVE:
> -			cf->can_id |= CAN_ERR_CNT;
> -			cf->data[1] = CAN_ERR_CRTL_ACTIVE;
> -			cf->data[6] = bec.txerr;
> -			cf->data[7] = bec.rxerr;
> +			if (skb) {
> +				cf->can_id |= CAN_ERR_CNT;
> +				cf->data[1] = CAN_ERR_CRTL_ACTIVE;
> +				cf->data[6] = bec.txerr;
> +				cf->data[7] = bec.rxerr;
> +			}
>  			break;
>  		default:
>  			netdev_warn(ndev, "unhandled error state (%d:%s)!\n",


-- 

                Pavel Pisa
    phone:      +420 603531357
    e-mail:     pisa@cmp.felk.cvut.cz
    Department of Control Engineering FEE CVUT
    Karlovo namesti 13, 121 35, Prague 2
    university: http://control.fel.cvut.cz/
    personal:   http://cmp.felk.cvut.cz/~pisa
    social:     https://social.kernel.org/ppisa
    projects:   https://www.openhub.net/accounts/ppisa
    CAN related:http://canbus.pages.fel.cvut.cz/
    RISC-V education: https://comparch.edu.cvut.cz/
    Open Technologies Research Education and Exchange Services
    https://gitlab.fel.cvut.cz/otrees/org/-/wikis/home

^ permalink raw reply

* [PATCH] can: ctucanfd: handle skb allocation failure
From: Fedor Pchelkin @ 2025-01-14 15:21 UTC (permalink / raw)
  To: Pavel Pisa, Marc Kleine-Budde
  Cc: Fedor Pchelkin, Ondrej Ille, Vincent Mailhol, Martin Jerabek,
	linux-can, linux-kernel, lvc-project, stable

If skb allocation fails, the pointer to struct can_frame is NULL. This
is actually handled everywhere inside ctucan_err_interrupt() except for
the only place.

Add the missed NULL check.

Found by Linux Verification Center (linuxtesting.org) with SVACE static
analysis tool.

Fixes: 2dcb8e8782d8 ("can: ctucanfd: add support for CTU CAN FD open-source IP core - bus independent part.")
Cc: stable@vger.kernel.org
Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
---
 drivers/net/can/ctucanfd/ctucanfd_base.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/net/can/ctucanfd/ctucanfd_base.c b/drivers/net/can/ctucanfd/ctucanfd_base.c
index 64c349fd4600..f65c1a1e05cc 100644
--- a/drivers/net/can/ctucanfd/ctucanfd_base.c
+++ b/drivers/net/can/ctucanfd/ctucanfd_base.c
@@ -867,10 +867,12 @@ static void ctucan_err_interrupt(struct net_device *ndev, u32 isr)
 			}
 			break;
 		case CAN_STATE_ERROR_ACTIVE:
-			cf->can_id |= CAN_ERR_CNT;
-			cf->data[1] = CAN_ERR_CRTL_ACTIVE;
-			cf->data[6] = bec.txerr;
-			cf->data[7] = bec.rxerr;
+			if (skb) {
+				cf->can_id |= CAN_ERR_CNT;
+				cf->data[1] = CAN_ERR_CRTL_ACTIVE;
+				cf->data[6] = bec.txerr;
+				cf->data[7] = bec.rxerr;
+			}
 			break;
 		default:
 			netdev_warn(ndev, "unhandled error state (%d:%s)!\n",
-- 
2.39.5


^ permalink raw reply related

* Re: [PATCH v5 4/7] can: Add Nuvoton NCT6694 CAN support
From: Vincent Mailhol @ 2025-01-14 15:11 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc, linux-usb
In-Reply-To: <CAOoeyxW=k35-bkeqNmhyZwUxjy=g3irTBS5mbXLxqp1Stx-Zfg@mail.gmail.com>

On 14/01/2025 at 19:46, Ming Yu wrote:
> Dear Vincent,
> 
> Thank you for your reply,
> I'll add comments to describe these locks in the next patch,
> 
> Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2025年1月14日 週二 下午4:06寫道:

(...)

>>> +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
>>> +                                       struct can_berr_counter *bec)
>>> +{
>>> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
>>> +       struct nct6694_can_event *evt = priv->rx->event;
>>> +       struct nct6694_cmd_header cmd_hd;
>>> +       u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
>>> +       int ret;
>>> +
>>> +       guard(mutex)(&priv->lock);
>>> +
>>> +       cmd_hd = (struct nct6694_cmd_header) {
>>> +               .mod = NCT6694_CAN_MOD,
>>> +               .cmd = NCT6694_CAN_EVENT,
>>> +               .sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask),
>>> +               .len = cpu_to_le16(sizeof(priv->rx->event))
>>> +       };
>>> +
>>> +       ret = nct6694_read_msg(priv->nct6694, &cmd_hd, evt);
>>> +       if (ret < 0)
>>> +               return ret;
>>
>> You are holding the priv->lock mutex before calling
>> nct6694_read_msg(). But nct6694_read_msg() then holds the
>> nct6694->access_lock mutex. Why do you need a double mutex here? What
>> kind of race scenario are you trying to prevent here?
>>
> 
> I think priv->lock need to be placed here to prevent priv->rx from
> being assigned by other functions, and nct6694->access_lock ensures
> that the nct6694_read_msg() transaction is completed.
> But in this case, cmd_hd does not need to be in priv->lock's scope.

So, the only reason for holding priv->lock is because priv->rx is shared
between functions.

struct nct6694_can_event is only 8 bytes. And you only need it for the
life time of the function so it can simply be declared on the stack:

  	struct nct6694_can_event evt;

and with this, no more need to hold the lock. And the same thing also
applies to the other functions.

Here, by trying to optimize the memory for only a few bytes, you are
getting a huge penalty on the performance by putting locks on all the
functions. This is not a good tradeoff.

>>> +       bec->rxerr = evt[priv->can_idx].rec;
>>> +       bec->txerr = evt[priv->can_idx].tec;
>>> +
>>> +       return 0;
>>> +}


Yours sincerely,
Vincent Mailhol


^ permalink raw reply

* Re: [PATCH v5 2/7] gpio: Add Nuvoton NCT6694 GPIO support
From: Bartosz Golaszewski @ 2025-01-14 12:12 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni, linux-kernel, linux-gpio, linux-i2c,
	linux-can, netdev, linux-watchdog, linux-hwmon, linux-rtc,
	linux-usb
In-Reply-To: <20250114033010.2445925-3-a0282524688@gmail.com>

On Tue, Jan 14, 2025 at 4:30 AM Ming Yu <a0282524688@gmail.com> wrote:
>
> This driver supports GPIO and IRQ functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
> ---

Please pick up review tags when resending.

Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>

^ permalink raw reply

* Re: [PATCH v5 4/7] can: Add Nuvoton NCT6694 CAN support
From: Ming Yu @ 2025-01-14 10:46 UTC (permalink / raw)
  To: Vincent Mailhol
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc, linux-usb
In-Reply-To: <CAMZ6RqLHEoukxDfV33iDWXjM1baK922QnWSkOP01VzZ0S_9H8g@mail.gmail.com>

Dear Vincent,

Thank you for your reply,
I'll add comments to describe these locks in the next patch,

Vincent Mailhol <mailhol.vincent@wanadoo.fr> 於 2025年1月14日 週二 下午4:06寫道:
>
...
> > +config CAN_NCT6694
> > +       tristate "Nuvoton NCT6694 Socket CANfd support"
> > +       depends on MFD_NCT6694
>
> Your driver uses the CAN rx offload. You need to select it here.
>
>           select CAN_RX_OFFLOAD
>

Understood! I'll add it in v6.

> > +       help
> > +         If you say yes to this option, support will be included for Nuvoton
> > +         NCT6694, a USB device to socket CANfd controller.
> > +
> > +         This driver can also be built as a module. If so, the module will
> > +         be called nct6694_canfd.
>
> Here, the name is nct6694_canfd...
>
> >  config CAN_PEAK_USB
> >         tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
> >         help
> > diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
> > index 8b11088e9a59..fcafb1ac262e 100644
> > --- a/drivers/net/can/usb/Makefile
> > +++ b/drivers/net/can/usb/Makefile
> > @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
> >  obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
> >  obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
> >  obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
> > +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
> >  obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
> >  obj-$(CONFIG_CAN_UCAN) += ucan.o
> > diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> > new file mode 100644
> > index 000000000000..7a15c39021ff
> > --- /dev/null
> > +++ b/drivers/net/can/usb/nct6694_canfd.c
> > @@ -0,0 +1,856 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Nuvoton NCT6694 Socket CANfd driver based on USB interface.
> > + *
> > + * Copyright (C) 2024 Nuvoton Technology Corp.
> > + */
> > +
> > +#include <linux/can/dev.h>
> > +#include <linux/can/rx-offload.h>
> > +#include <linux/ethtool.h>
> > +#include <linux/irqdomain.h>
> > +#include <linux/kernel.h>
> > +#include <linux/mfd/core.h>
> > +#include <linux/mfd/nct6694.h>
> > +#include <linux/module.h>
> > +#include <linux/netdevice.h>
> > +#include <linux/platform_device.h>
> > +
> > +#define DRVNAME "nct6694-can"
>
> ... but here, it is nct6694-can.
>
> Use a consistent name between the module name and the driver name.
>

Okay, Fix it in v6.

> > +/*
> > + * USB command module type for NCT6694 CANfd controller.
> > + * This defines the module type used for communication with the NCT6694
> > + * CANfd controller over the USB interface.
> > + */
> > +#define NCT6694_CAN_MOD                        0x05
> > +
> > +/* Command 00h - CAN Setting and Initialization */
> > +#define NCT6694_CAN_SETTING            0x00
> > +#define NCT6694_CAN_SETTING_SEL(idx)   (idx ? 0x01 : 0x00)
>
> What are the possible values for idx? Isn't it only 0 or 1? If so, no
> need for this NCT6694_CAN_SETTING_SEL() macro. Directly assign the
> channel index to the selector field.
>

Okay, Fix it in v6.

> > +#define NCT6694_CAN_SETTING_CTRL1_MON  BIT(0)
> > +#define NCT6694_CAN_SETTING_CTRL1_NISO BIT(1)
> > +#define NCT6694_CAN_SETTING_CTRL1_LBCK BIT(2)
> > +
> > +/* Command 01h - CAN Information */
> > +#define NCT6694_CAN_INFORMATION                0x01
> > +#define NCT6694_CAN_INFORMATION_SEL    0x00
> > +
> > +/* Command 02h - CAN Event */
> > +#define NCT6694_CAN_EVENT              0x02
> > +#define NCT6694_CAN_EVENT_SEL(idx, mask)       \
> > +       ((idx ? 0x80 : 0x00) | ((mask) & 0xFF))
>
> Can idx and mask really overlap? Shouldn't this be:
>
>   #define NCT6694_CAN_EVENT_SEL(idx, mask)  \
>           ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))
>

Sorry, you're right, I'll fix it in v6.

> > +#define NCT6694_CAN_EVENT_ERR          BIT(0)
> > +#define NCT6694_CAN_EVENT_STATUS       BIT(1)
> > +#define NCT6694_CAN_EVENT_TX_EVT       BIT(2)
> > +#define NCT6694_CAN_EVENT_RX_EVT       BIT(3)
> > +#define NCT6694_CAN_EVENT_REC          BIT(4)
> > +#define NCT6694_CAN_EVENT_TEC          BIT(5)
> > +#define NCT6694_CAN_EVENT_MASK         GENMASK(3, 0)
> > +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY  BIT(7)  /* Read-clear */
> > +#define NCT6694_CAN_EVT_RX_DATA_LOST   BIT(5)  /* Read-clear */
> > +#define NCT6694_CAN_EVT_RX_HALF_FULL   BIT(6)  /* Read-clear */
> > +#define NCT6694_CAN_EVT_RX_DATA_IN     BIT(7)  /* Read-clear*/
>
> Some of those macro are not used:
>
>   drivers/net/can/usb/nct6694_canfd.c:52: warning: macro
> "NCT6694_CAN_EVT_RX_HALF_FULL" is not used [-Wunused-macros]
>      52 | #define NCT6694_CAN_EVT_RX_HALF_FULL BIT(6) /* Read-clear */
>         |
>   drivers/net/can/usb/nct6694_canfd.c:43: warning: macro
> "NCT6694_CAN_EVENT_ERR" is not used [-Wunused-macros]
>      43 | #define NCT6694_CAN_EVENT_ERR  BIT(0)
>         |
>   drivers/net/can/usb/nct6694_canfd.c:44: warning: macro
> "NCT6694_CAN_EVENT_STATUS" is not used [-Wunused-macros]
>      44 | #define NCT6694_CAN_EVENT_STATUS BIT(1)
>         |
>   drivers/net/can/usb/nct6694_canfd.c:46: warning: macro
> "NCT6694_CAN_EVENT_RX_EVT" is not used [-Wunused-macros]
>      46 | #define NCT6694_CAN_EVENT_RX_EVT BIT(3)
>         |
>   drivers/net/can/usb/nct6694_canfd.c:45: warning: macro
> "NCT6694_CAN_EVENT_TX_EVT" is not used [-Wunused-macros]
>      45 | #define NCT6694_CAN_EVENT_TX_EVT BIT(2)
>         |
>
> Is this OK?
>

Yes, these macros are replaced by  NCT6694_CAN_EVENT_MASK, I'll drop
them in the next patch.

> > +/* Command 10h - CAN Deliver */
> > +#define NCT6694_CAN_DELIVER            0x10
> > +#define NCT6694_CAN_DELIVER_SEL(buf_cnt)       \
> > +       ((buf_cnt) & 0xFF)
> > +
> > +/* Command 11h - CAN Receive */
> > +#define NCT6694_CAN_RECEIVE            0x11
> > +#define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt)  \
> > +       ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0xFF))
>
> Can idx and buf_cnt really overlap? Shouldn't this be:
>
>   #define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt)  \
>           ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))
>

Fix it in v6.

> > +#define NCT6694_CAN_FRAME_TAG_CAN0     0xC0
> > +#define NCT6694_CAN_FRAME_TAG_CAN1     0xC1
> > +#define NCT6694_CAN_FRAME_FLAG_EFF     BIT(0)
> > +#define NCT6694_CAN_FRAME_FLAG_RTR     BIT(1)
> > +#define NCT6694_CAN_FRAME_FLAG_FD      BIT(2)
> > +#define NCT6694_CAN_FRAME_FLAG_BRS     BIT(3)
> > +#define NCT6694_CAN_FRAME_FLAG_ERR     BIT(4)
> > +
> > +#define NCT6694_NAPI_WEIGHT            32
> > +
> > +enum nct6694_event_err {
> > +       NCT6694_CAN_EVT_ERR_NO_ERROR = 0,
> > +       NCT6694_CAN_EVT_ERR_CRC_ERROR,
> > +       NCT6694_CAN_EVT_ERR_STUFF_ERROR,
> > +       NCT6694_CAN_EVT_ERR_ACK_ERROR,
> > +       NCT6694_CAN_EVT_ERR_FORM_ERROR,
> > +       NCT6694_CAN_EVT_ERR_BIT_ERROR,
> > +       NCT6694_CAN_EVT_ERR_TIMEOUT_ERROR,
> > +       NCT6694_CAN_EVT_ERR_UNKNOWN_ERROR,
> > +};
> > +
> > +enum nct6694_event_status {
> > +       NCT6694_CAN_EVT_STS_ERROR_ACTIVE = 0,
> > +       NCT6694_CAN_EVT_STS_ERROR_PASSIVE,
> > +       NCT6694_CAN_EVT_STS_BUS_OFF,
> > +       NCT6694_CAN_EVT_STS_WARNING,
> > +};
> > +
> > +struct __packed nct6694_can_setting {
> > +       __le32 nbr;
> > +       __le32 dbr;
> > +       u8 active;
> > +       u8 reserved[3];
> > +       __le16 ctrl1;
> > +       __le16 ctrl2;
> > +       __le32 nbtp;
> > +       __le32 dbtp;
> > +};
> > +
> > +struct __packed nct6694_can_information {
> > +       u8 tx_fifo_cnt;
> > +       u8 rx_fifo_cnt;
> > +       u8 reserved[2];
> > +       __le32 can_clk;
> > +};
> > +
> > +struct __packed nct6694_can_event {
> > +       u8 err;
> > +       u8 status;
> > +       u8 tx_evt;
> > +       u8 rx_evt;
> > +       u8 rec;
> > +       u8 tec;
> > +       u8 reserved[2];
> > +};
> > +
> > +struct __packed nct6694_can_frame {
> > +       u8 tag;
> > +       u8 flag;
> > +       u8 reserved;
> > +       u8 length;
> > +       __le32 id;
> > +       u8 data[64];
>
> Nitpick, use CANFD_MAX_DLEN here:
>
>           u8 data[CANFD_MAX_DLEN];
>

Fix it in v6.

> > +};
> > +
...
> > +static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
> > +{
> > +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +       struct nct6694_can_frame *frame = &priv->rx->frame;
> > +       struct nct6694_cmd_header cmd_hd = {
> > +               .mod = NCT6694_CAN_MOD,
> > +               .cmd = NCT6694_CAN_RECEIVE,
> > +               .sel = NCT6694_CAN_RECEIVE_SEL(priv->can_idx, 1),
> > +               .len = cpu_to_le16(sizeof(*frame))
> > +       };
> > +       struct canfd_frame *cfd;
> > +       struct can_frame *cf;
> > +       struct sk_buff *skb;
> > +       int ret;
> > +
> > +       ret = nct6694_read_msg(priv->nct6694, &cmd_hd, frame);
> > +       if (ret)
> > +               return;
> > +
> > +       if (frame->flag & NCT6694_CAN_FRAME_FLAG_FD) {
>
> Reduce scope of variable when possible: move declaration of cfd here:
>
>                 struct canfd_frame *cfd;
>

Okay! Fix it in v6.

> > +               skb = alloc_canfd_skb(priv->ndev, &cfd);
> > +               if (!skb)
> > +                       return;
> > +
> > +               cfd->can_id = le32_to_cpu(frame->id);
> > +               cfd->len = frame->length;
>
> No. I asked you to sanitize the length in this message:
>
>   https://lore.kernel.org/linux-can/8d66cf66-5564-4272-8c3e-51b715c3d785@wanadoo.fr/
>
> Never use the length as is.
>

Sorry! I misunderstood your meaning.
I'll Fix it to cfd->len = canfd_sanitize_len(frame->length).

> > +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> > +                       cfd->can_id |= CAN_EFF_FLAG;
> > +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
> > +                       cfd->flags |= CANFD_BRS;
> > +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_ERR)
> > +                       cfd->flags |= CANFD_ESI;
> > +
> > +               memcpy(cfd->data, frame->data, cfd->len);
> > +       } else {
>
> Reduce scope of variable when possible: move declaration of cf here:
>
>                 struct canfd_frame *cf;
>

Fix it in v6.

> > +               skb = alloc_can_skb(priv->ndev, &cf);
> > +               if (!skb)
> > +                       return;
> > +
> > +               cf->can_id = le32_to_cpu(frame->id);
> > +               cf->len = frame->length;
>
> Ditto, sanitize the length.
>

Fix it in v6.

> > +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> > +                       cf->can_id |= CAN_EFF_FLAG;
> > +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
> > +                       cf->can_id |= CAN_RTR_FLAG;
> > +
> > +               memcpy(cf->data, frame->data, cf->len);
>
> Only copy can data if the frame is not an RTR frame.
>
>                   if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
>                           cf->can_id |= CAN_RTR_FLAG;
>                   else
>                           memcpy(cf->data, frame->data, cf->len);
>
> I already asked you to do this in below comment:
>
>   https://lore.kernel.org/linux-can/a25ea362-142f-4e27-8194-787d9829f607@wanadoo.fr/
>

Sorry for forgetting the part, I'll fix it in the next patch.

> > +       }
> > +
> > +       nct6694_can_rx_offload(&priv->offload, skb);
> > +}
> > +
> > +static void nct6694_can_clean(struct net_device *ndev)
> > +{
> > +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +
> > +       if (priv->tx_skb || netif_queue_stopped(ndev))
> > +               ndev->stats.tx_errors++;
> > +       dev_kfree_skb(priv->tx_skb);
> > +       priv->tx_skb = NULL;
> > +}
> > +
> > +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> > +                                       struct can_berr_counter *bec)
> > +{
> > +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +       struct nct6694_can_event *evt = priv->rx->event;
> > +       struct nct6694_cmd_header cmd_hd;
> > +       u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
> > +       int ret;
> > +
> > +       guard(mutex)(&priv->lock);
> > +
> > +       cmd_hd = (struct nct6694_cmd_header) {
> > +               .mod = NCT6694_CAN_MOD,
> > +               .cmd = NCT6694_CAN_EVENT,
> > +               .sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask),
> > +               .len = cpu_to_le16(sizeof(priv->rx->event))
> > +       };
> > +
> > +       ret = nct6694_read_msg(priv->nct6694, &cmd_hd, evt);
> > +       if (ret < 0)
> > +               return ret;
>
> You are holding the priv->lock mutex before calling
> nct6694_read_msg(). But nct6694_read_msg() then holds the
> nct6694->access_lock mutex. Why do you need a double mutex here? What
> kind of race scenario are you trying to prevent here?
>

I think priv->lock need to be placed here to prevent priv->rx from
being assigned by other functions, and nct6694->access_lock ensures
that the nct6694_read_msg() transaction is completed.
But in this case, cmd_hd does not need to be in priv->lock's scope.

> > +       bec->rxerr = evt[priv->can_idx].rec;
> > +       bec->txerr = evt[priv->can_idx].tec;
> > +
> > +       return 0;
> > +}
> > +
> > +static void nct6694_can_handle_state_change(struct net_device *ndev,
> > +                                           u8 status)
> > +{
> > +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +       enum can_state new_state = priv->can.state;
> > +       enum can_state rx_state, tx_state;
> > +       struct can_berr_counter bec;
> > +       struct can_frame *cf;
> > +       struct sk_buff *skb;
> > +
> > +       nct6694_can_get_berr_counter(ndev, &bec);
> > +       can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state);
>
> Here, you set up tx_state and rx_state...
>
> > +       switch (status) {
> > +       case NCT6694_CAN_EVT_STS_ERROR_ACTIVE:
> > +               new_state = CAN_STATE_ERROR_ACTIVE;
> > +               break;
> > +       case NCT6694_CAN_EVT_STS_ERROR_PASSIVE:
> > +               new_state = CAN_STATE_ERROR_PASSIVE;
> > +               break;
> > +       case NCT6694_CAN_EVT_STS_BUS_OFF:
> > +               new_state = CAN_STATE_BUS_OFF;
> > +               break;
> > +       case NCT6694_CAN_EVT_STS_WARNING:
> > +               new_state = CAN_STATE_ERROR_WARNING;
> > +               break;
> > +       default:
> > +               netdev_err(ndev, "Receive unknown CAN status event.\n");
> > +               return;
> > +       }
> > +
> > +       /* state hasn't changed */
> > +       if (new_state == priv->can.state)
> > +               return;
> > +
> > +       skb = alloc_can_err_skb(ndev, &cf);
> > +
> > +       tx_state = bec.txerr >= bec.rxerr ? new_state : 0;
> > +       rx_state = bec.txerr <= bec.rxerr ? new_state : 0;
>
> ... but you never used the values returned by
> can_state_get_by_berr_counter() and just overwrote the tx and rx
> state.
>
> What is the logic here? Why do you need to manually adjust those two
> values? Isn't the logic in can_change_state() sufficient?
>
> > +       can_change_state(ndev, cf, tx_state, rx_state);
> > +
> > +       if (new_state == CAN_STATE_BUS_OFF) {
>
> Same for the new_state. The function can_change_state() calculate the
> new state from tx_state and rx_state and save it under
> can_priv->state. But here, you do your own calculation.
>
> Only keep one of the two. If your device already tells you the state,
> then fine! Just use the information from your device and do not use
> can_change_state(). Here, you are doing double work resulting in a
> weird mix.
>

Okay! I will revert nct6694_can_handle_state_change() back to the v3 version.

> > +               can_bus_off(ndev);
> > +       } else if (skb) {
> > +               cf->can_id |= CAN_ERR_CNT;
> > +               cf->data[6] = bec.txerr;
> > +               cf->data[7] = bec.rxerr;
> > +       }
> > +
> > +       nct6694_can_rx_offload(&priv->offload, skb);
> > +}
> > +
...
> > +static irqreturn_t nct6694_can_irq(int irq, void *data)
> > +{
> > +       struct net_device *ndev = data;
> > +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +       struct nct6694_can_event *evt = priv->rx->event;
> > +       struct nct6694_cmd_header cmd_hd;
> > +       u8 tx_evt, rx_evt, bus_err, can_status;
> > +       u8 mask_sts = NCT6694_CAN_EVENT_MASK;
>
> No need for the mask_sts variable. Directly use NCT6694_CAN_EVENT_MASK.
>

Okay! Fix it in v6.

> > +       irqreturn_t handled = IRQ_NONE;
> > +       int can_idx = priv->can_idx;
> > +       int ret;
> > +
> > +       scoped_guard(mutex, &priv->lock) {
>
> Reduce scope of variable when possible: move the declarations of
> cmd_hd and ret here.
>

Okay! Fix it in v6.

> > +               cmd_hd = (struct nct6694_cmd_header) {
> > +                       .mod = NCT6694_CAN_MOD,
> > +                       .cmd = NCT6694_CAN_EVENT,
> > +                       .sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask_sts),
> > +                       .len = cpu_to_le16(sizeof(priv->rx->event))
> > +               };
> > +
...
> > +static void nct6694_can_tx(struct net_device *ndev)
> > +{
> > +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> > +       struct nct6694_can_frame *frame = &priv->tx->frame;
> > +       struct nct6694_cmd_header cmd_hd = {
> > +               .mod = NCT6694_CAN_MOD,
> > +               .cmd = NCT6694_CAN_DELIVER,
> > +               .sel = NCT6694_CAN_DELIVER_SEL(1),
> > +               .len = cpu_to_le16(sizeof(*frame))
> > +       };
> > +       struct net_device_stats *stats = &ndev->stats;
> > +       struct sk_buff *skb = priv->tx_skb;
> > +       struct canfd_frame *cfd;
> > +       struct can_frame *cf;
> > +       u32 txid;
> > +       int err;
> > +
> > +       memset(frame, 0, sizeof(*frame));
> > +
> > +       if (priv->can_idx == 0)
> > +               frame->tag = NCT6694_CAN_FRAME_TAG_CAN0;
> > +       else
> > +               frame->tag = NCT6694_CAN_FRAME_TAG_CAN1;
> > +
> > +       if (can_is_canfd_skb(skb)) {
>
> Reduce scope of variable when possible: move declaration of cfd here:
>
>                 struct canfd_frame *cfd;
>

Okay! Fix it in v6.

> > +               cfd = (struct canfd_frame *)priv->tx_skb->data;
> > +
> > +               if (cfd->flags & CANFD_BRS)
> > +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_BRS;
> > +
> > +               if (cfd->can_id & CAN_EFF_FLAG) {
> > +                       txid = cfd->can_id & CAN_EFF_MASK;
> > +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> > +               } else {
> > +                       txid = cfd->can_id & CAN_SFF_MASK;
> > +               }
> > +               frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
> > +               frame->id = cpu_to_le32(txid);
> > +               frame->length = cfd->len;
> > +
> > +               memcpy(frame->data, cfd->data, cfd->len);
> > +       } else {
>
> Reduce scope of variable when possible: move declaration of cf here:
>
>                 struct canfd_frame *cf;
>

Okay! Fix it in v6.

> > +               cf = (struct can_frame *)priv->tx_skb->data;
> > +
> > +               if (cf->can_id & CAN_RTR_FLAG)
> > +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_RTR;
> > +
> > +               if (cf->can_id & CAN_EFF_FLAG) {
> > +                       txid = cf->can_id & CAN_EFF_MASK;
> > +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> > +               } else {
> > +                       txid = cf->can_id & CAN_SFF_MASK;
> > +               }
> > +               frame->id = cpu_to_le32(txid);
> > +               frame->length = cf->len;
> > +
> > +               memcpy(frame->data, cf->data, cf->len);
>
> Don't copy cf->data if the can frame is a RTR frame.
>

Okay! Fix it in v6.

> > +       }
> > +
> > +       err = nct6694_write_msg(priv->nct6694, &cmd_hd, frame);
> > +       if (err) {
> > +               netdev_err(ndev, "%s: Tx FIFO full!\n", __func__);
> > +               can_free_echo_skb(ndev, 0, NULL);
> > +               stats->tx_dropped++;
> > +               stats->tx_errors++;
> > +               netif_wake_queue(ndev);
> > +       }
> > +}
> > +
...
> > +static int nct6694_can_probe(struct platform_device *pdev)
> > +{
> > +       const struct mfd_cell *cell = mfd_get_cell(pdev);
> > +       struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> > +       struct nct6694_can_priv *priv;
> > +       struct net_device *ndev;
> > +       int ret, irq, can_clk;
> > +
> > +       irq = irq_create_mapping(nct6694->domain,
> > +                                NCT6694_IRQ_CAN1 + cell->id);
> > +       if (!irq)
> > +               return irq;
> > +
> > +       ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> > +       if (!ndev)
> > +               return -ENOMEM;
> > +
> > +       ndev->irq = irq;
> > +       ndev->flags |= IFF_ECHO;
> > +       ndev->netdev_ops = &nct6694_can_netdev_ops;
> > +       ndev->ethtool_ops = &nct6694_can_ethtool_ops;
>
> Your device has two CAN interfaces, right? Do not forget to populate
> netdev->dev_port.
>
>           netdev->dev_port = cell->id;
>

Okay! I'll add it in v6.

> > +       priv = netdev_priv(ndev);
> > +       priv->nct6694 = nct6694;
> > +       priv->ndev = ndev;
> > +

Best regards,
Ming

^ permalink raw reply

* Re: [PATCH net-next v8 06/11] net: ipv6: Use link netns in newlink() of rtnl_link_ops
From: Xiao Liang @ 2025-01-14  9:02 UTC (permalink / raw)
  To: Kuniyuki Iwashima
  Cc: alex.aring, andrew+netdev, b.a.t.m.a.n, bpf, bridge, davem,
	donald.hunter, dsahern, edumazet, herbert, horms, kuba, linux-can,
	linux-kernel, linux-kselftest, linux-ppp, linux-rdma,
	linux-wireless, linux-wpan, miquel.raynal, netdev,
	osmocom-net-gprs, pabeni, shuah, stefan, steffen.klassert,
	wireguard
In-Reply-To: <20250114044935.26418-1-kuniyu@amazon.com>

On Tue, Jan 14, 2025 at 12:49 PM Kuniyuki Iwashima <kuniyu@amazon.com> wrote:
>
> From: Xiao Liang <shaw.leon@gmail.com>
> Date: Mon, 13 Jan 2025 22:37:14 +0800
> > diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c
> > index 2a6a424806aa..ac5e402c34bc 100644
> > --- a/drivers/net/bonding/bond_netlink.c
> > +++ b/drivers/net/bonding/bond_netlink.c
> > @@ -564,10 +564,12 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[],
> >       return 0;
> >  }
> >
> > -static int bond_newlink(struct net *src_net, struct net_device *bond_dev,
> > -                     struct nlattr *tb[], struct nlattr *data[],
> > +static int bond_newlink(struct net_device *bond_dev,
> > +                     struct rtnl_newlink_params *params,
> >                       struct netlink_ext_ack *extack)
> >  {
> > +     struct nlattr **data = params->data;
> > +     struct nlattr **tb = params->tb;
> >       int err;
> >
> >       err = bond_changelink(bond_dev, tb, data, extack);
>
> Note that IFLA_BOND_ACTIVE_SLAVE uses dev_net(dev) for
> __dev_get_by_index().

That's true. Bond devices have no "link-netns", and a slave
device must be in the same namespace of the main dev.

> [...]
> > diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
> > index fed4fe2a4748..0c496aa1f706 100644
> > --- a/drivers/net/macvlan.c
> > +++ b/drivers/net/macvlan.c
> > @@ -1565,11 +1565,12 @@ int macvlan_common_newlink(struct net *src_net, struct net_device *dev,
> >  }
> >  EXPORT_SYMBOL_GPL(macvlan_common_newlink);
> >
> > -static int macvlan_newlink(struct net *src_net, struct net_device *dev,
> > -                        struct nlattr *tb[], struct nlattr *data[],
> > +static int macvlan_newlink(struct net_device *dev,
> > +                        struct rtnl_newlink_params *params,
> >                          struct netlink_ext_ack *extack)
> >  {
> > -     return macvlan_common_newlink(src_net, dev, tb, data, extack);
> > +     return macvlan_common_newlink(params->net, dev, params->tb,
> > +                                   params->data, extack);
>
> Pass params as is as you did for ipvlan_link_new().
>
> Same for macvtap_newlink().

OK.

> [...]
> > diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
> > index 1e1b00756be7..1e9eadc77da2 100644
> > --- a/drivers/net/netkit.c
> > +++ b/drivers/net/netkit.c
> > @@ -327,10 +327,13 @@ static int netkit_validate(struct nlattr *tb[], struct nlattr *data[],
> >
> >  static struct rtnl_link_ops netkit_link_ops;
> >
> > -static int netkit_new_link(struct net *peer_net, struct net_device *dev,
> > -                        struct nlattr *tb[], struct nlattr *data[],
> > +static int netkit_new_link(struct net_device *dev,
> > +                        struct rtnl_newlink_params *params,
> >                          struct netlink_ext_ack *extack)
> >  {
> > +     struct nlattr **data = params->data;
> > +     struct net *peer_net = params->net;
> > +     struct nlattr **tb = params->tb;
>
> nit: please keep the reverse xmas tree order.
>
>
> >       struct nlattr *peer_tb[IFLA_MAX + 1], **tbp = tb, *attr;
>
> you can define *tbp here and initialise it later.
>
>         struct nlattr *peer_tb[IFLA_MAX + 1], **tbp, *attr;
>
> >       enum netkit_action policy_prim = NETKIT_PASS;
> >       enum netkit_action policy_peer = NETKIT_PASS;
>
>
> [...]
> > @@ -1064,6 +1067,11 @@ static void wwan_create_default_link(struct wwan_device *wwandev,
> >       struct net_device *dev;
> >       struct nlmsghdr *nlh;
> >       struct sk_buff *msg;
> > +     struct rtnl_newlink_params params = {
> > +             .net = &init_net,
> > +             .tb = tb,
> > +             .data = data,
> > +     };
>
> nit: Reverse xmas tree order
>
>
> [...]
> > diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
> > index ec98349b9620..7ff5e96f6ba7 100644
> > --- a/net/core/rtnetlink.c
> > +++ b/net/core/rtnetlink.c
> > @@ -3766,6 +3766,14 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
> >       struct net_device *dev;
> >       char ifname[IFNAMSIZ];
> >       int err;
> > +     struct rtnl_newlink_params params = {
>
> nit: Reverse xmas tree order
>
>
> > +             .net = net,
>
> Use sock_net(skb->sk) directly here and remove net defined above,
> which is no longer used in this function.
>
> ---8<---
>         unsigned char name_assign_type = NET_NAME_USER;
>         struct rtnl_newlink_params params = {
>                 .net = sock_net(skb->sk),
>                 .src_net = net,
>                 .link_net = link_net,
>                 .peer_net = peer_net,
>                 .tb = tb,
>                 .data = data,
>         };
>         u32 portid = NETLINK_CB(skb).portid;
> ---8<---
>
>
> [...]
> > @@ -1698,6 +1702,10 @@ struct net_device *gretap_fb_dev_create(struct net *net, const char *name,
> >       LIST_HEAD(list_kill);
> >       struct ip_tunnel *t;
> >       int err;
> > +     struct rtnl_newlink_params params = {
> > +             .net = net,
> > +             .tb = tb,
> > +     };
> >
> >       memset(&tb, 0, sizeof(tb));
>
> nit: Reverse xmas tree

Will fix the style issues mentioned above in the next version.

Thanks.

^ permalink raw reply

* Re: [PATCH v5 4/7] can: Add Nuvoton NCT6694 CAN support
From: Vincent Mailhol @ 2025-01-14  8:05 UTC (permalink / raw)
  To: Ming Yu
  Cc: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, andrew+netdev,
	davem, edumazet, kuba, pabeni, wim, linux, jdelvare,
	alexandre.belloni, linux-kernel, linux-gpio, linux-i2c, linux-can,
	netdev, linux-watchdog, linux-hwmon, linux-rtc, linux-usb
In-Reply-To: <20250114033010.2445925-5-a0282524688@gmail.com>

Hi Ming,

Now the structure finally looks good! I did a deeper review than before.

Right now, I am mostly concerned of the double use of mutexes:

  - nct6694_can_priv->lock in this can driver
  - nct6694->access_lock in the core driver

checkpatch.pl actually suggests you to add comments to those mutexes:

  CHECK: struct mutex definition without comment
  #146: FILE: drivers/net/can/usb/nct6694_canfd.c:146:
  +      struct mutex lock;

On Tue. 14 Jan 2025 at 12:32, Ming Yu <a0282524688@gmail.com> wrote:
> This driver supports Socket CANfd functionality for NCT6694 MFD
> device based on USB interface.
>
> Signed-off-by: Ming Yu <a0282524688@gmail.com>
> ---
>  MAINTAINERS                         |   1 +
>  drivers/net/can/usb/Kconfig         |  10 +
>  drivers/net/can/usb/Makefile        |   1 +
>  drivers/net/can/usb/nct6694_canfd.c | 856 ++++++++++++++++++++++++++++
>  4 files changed, 868 insertions(+)
>  create mode 100644 drivers/net/can/usb/nct6694_canfd.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4e72f749cdf2..6e9b78202d6f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16724,6 +16724,7 @@ S:      Supported
>  F:     drivers/gpio/gpio-nct6694.c
>  F:     drivers/i2c/busses/i2c-nct6694.c
>  F:     drivers/mfd/nct6694.c
> +F:     drivers/net/can/usb/nct6694_canfd.c
>  F:     include/linux/mfd/nct6694.h
>
>  NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
> diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
> index 9dae0c71a2e1..53254012cdc4 100644
> --- a/drivers/net/can/usb/Kconfig
> +++ b/drivers/net/can/usb/Kconfig
> @@ -133,6 +133,16 @@ config CAN_MCBA_USB
>           This driver supports the CAN BUS Analyzer interface
>           from Microchip (http://www.microchip.com/development-tools/).
>
> +config CAN_NCT6694
> +       tristate "Nuvoton NCT6694 Socket CANfd support"
> +       depends on MFD_NCT6694

Your driver uses the CAN rx offload. You need to select it here.

          select CAN_RX_OFFLOAD

> +       help
> +         If you say yes to this option, support will be included for Nuvoton
> +         NCT6694, a USB device to socket CANfd controller.
> +
> +         This driver can also be built as a module. If so, the module will
> +         be called nct6694_canfd.

Here, the name is nct6694_canfd...

>  config CAN_PEAK_USB
>         tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
>         help
> diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
> index 8b11088e9a59..fcafb1ac262e 100644
> --- a/drivers/net/can/usb/Makefile
> +++ b/drivers/net/can/usb/Makefile
> @@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
>  obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
>  obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
>  obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
> +obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
>  obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
>  obj-$(CONFIG_CAN_UCAN) += ucan.o
> diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
> new file mode 100644
> index 000000000000..7a15c39021ff
> --- /dev/null
> +++ b/drivers/net/can/usb/nct6694_canfd.c
> @@ -0,0 +1,856 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Nuvoton NCT6694 Socket CANfd driver based on USB interface.
> + *
> + * Copyright (C) 2024 Nuvoton Technology Corp.
> + */
> +
> +#include <linux/can/dev.h>
> +#include <linux/can/rx-offload.h>
> +#include <linux/ethtool.h>
> +#include <linux/irqdomain.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/nct6694.h>
> +#include <linux/module.h>
> +#include <linux/netdevice.h>
> +#include <linux/platform_device.h>
> +
> +#define DRVNAME "nct6694-can"

... but here, it is nct6694-can.

Use a consistent name between the module name and the driver name.

> +/*
> + * USB command module type for NCT6694 CANfd controller.
> + * This defines the module type used for communication with the NCT6694
> + * CANfd controller over the USB interface.
> + */
> +#define NCT6694_CAN_MOD                        0x05
> +
> +/* Command 00h - CAN Setting and Initialization */
> +#define NCT6694_CAN_SETTING            0x00
> +#define NCT6694_CAN_SETTING_SEL(idx)   (idx ? 0x01 : 0x00)

What are the possible values for idx? Isn't it only 0 or 1? If so, no
need for this NCT6694_CAN_SETTING_SEL() macro. Directly assign the
channel index to the selector field.

> +#define NCT6694_CAN_SETTING_CTRL1_MON  BIT(0)
> +#define NCT6694_CAN_SETTING_CTRL1_NISO BIT(1)
> +#define NCT6694_CAN_SETTING_CTRL1_LBCK BIT(2)
> +
> +/* Command 01h - CAN Information */
> +#define NCT6694_CAN_INFORMATION                0x01
> +#define NCT6694_CAN_INFORMATION_SEL    0x00
> +
> +/* Command 02h - CAN Event */
> +#define NCT6694_CAN_EVENT              0x02
> +#define NCT6694_CAN_EVENT_SEL(idx, mask)       \
> +       ((idx ? 0x80 : 0x00) | ((mask) & 0xFF))

Can idx and mask really overlap? Shouldn't this be:

  #define NCT6694_CAN_EVENT_SEL(idx, mask)  \
          ((idx ? 0x80 : 0x00) | ((mask) & 0x7F))

> +#define NCT6694_CAN_EVENT_ERR          BIT(0)
> +#define NCT6694_CAN_EVENT_STATUS       BIT(1)
> +#define NCT6694_CAN_EVENT_TX_EVT       BIT(2)
> +#define NCT6694_CAN_EVENT_RX_EVT       BIT(3)
> +#define NCT6694_CAN_EVENT_REC          BIT(4)
> +#define NCT6694_CAN_EVENT_TEC          BIT(5)
> +#define NCT6694_CAN_EVENT_MASK         GENMASK(3, 0)
> +#define NCT6694_CAN_EVT_TX_FIFO_EMPTY  BIT(7)  /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_LOST   BIT(5)  /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_HALF_FULL   BIT(6)  /* Read-clear */
> +#define NCT6694_CAN_EVT_RX_DATA_IN     BIT(7)  /* Read-clear*/

Some of those macro are not used:

  drivers/net/can/usb/nct6694_canfd.c:52: warning: macro
"NCT6694_CAN_EVT_RX_HALF_FULL" is not used [-Wunused-macros]
     52 | #define NCT6694_CAN_EVT_RX_HALF_FULL BIT(6) /* Read-clear */
        |
  drivers/net/can/usb/nct6694_canfd.c:43: warning: macro
"NCT6694_CAN_EVENT_ERR" is not used [-Wunused-macros]
     43 | #define NCT6694_CAN_EVENT_ERR  BIT(0)
        |
  drivers/net/can/usb/nct6694_canfd.c:44: warning: macro
"NCT6694_CAN_EVENT_STATUS" is not used [-Wunused-macros]
     44 | #define NCT6694_CAN_EVENT_STATUS BIT(1)
        |
  drivers/net/can/usb/nct6694_canfd.c:46: warning: macro
"NCT6694_CAN_EVENT_RX_EVT" is not used [-Wunused-macros]
     46 | #define NCT6694_CAN_EVENT_RX_EVT BIT(3)
        |
  drivers/net/can/usb/nct6694_canfd.c:45: warning: macro
"NCT6694_CAN_EVENT_TX_EVT" is not used [-Wunused-macros]
     45 | #define NCT6694_CAN_EVENT_TX_EVT BIT(2)
        |

Is this OK?

> +/* Command 10h - CAN Deliver */
> +#define NCT6694_CAN_DELIVER            0x10
> +#define NCT6694_CAN_DELIVER_SEL(buf_cnt)       \
> +       ((buf_cnt) & 0xFF)
> +
> +/* Command 11h - CAN Receive */
> +#define NCT6694_CAN_RECEIVE            0x11
> +#define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt)  \
> +       ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0xFF))

Can idx and buf_cnt really overlap? Shouldn't this be:

  #define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt)  \
          ((idx ? 0x80 : 0x00) | ((buf_cnt) & 0x7F))

> +#define NCT6694_CAN_FRAME_TAG_CAN0     0xC0
> +#define NCT6694_CAN_FRAME_TAG_CAN1     0xC1
> +#define NCT6694_CAN_FRAME_FLAG_EFF     BIT(0)
> +#define NCT6694_CAN_FRAME_FLAG_RTR     BIT(1)
> +#define NCT6694_CAN_FRAME_FLAG_FD      BIT(2)
> +#define NCT6694_CAN_FRAME_FLAG_BRS     BIT(3)
> +#define NCT6694_CAN_FRAME_FLAG_ERR     BIT(4)
> +
> +#define NCT6694_NAPI_WEIGHT            32
> +
> +enum nct6694_event_err {
> +       NCT6694_CAN_EVT_ERR_NO_ERROR = 0,
> +       NCT6694_CAN_EVT_ERR_CRC_ERROR,
> +       NCT6694_CAN_EVT_ERR_STUFF_ERROR,
> +       NCT6694_CAN_EVT_ERR_ACK_ERROR,
> +       NCT6694_CAN_EVT_ERR_FORM_ERROR,
> +       NCT6694_CAN_EVT_ERR_BIT_ERROR,
> +       NCT6694_CAN_EVT_ERR_TIMEOUT_ERROR,
> +       NCT6694_CAN_EVT_ERR_UNKNOWN_ERROR,
> +};
> +
> +enum nct6694_event_status {
> +       NCT6694_CAN_EVT_STS_ERROR_ACTIVE = 0,
> +       NCT6694_CAN_EVT_STS_ERROR_PASSIVE,
> +       NCT6694_CAN_EVT_STS_BUS_OFF,
> +       NCT6694_CAN_EVT_STS_WARNING,
> +};
> +
> +struct __packed nct6694_can_setting {
> +       __le32 nbr;
> +       __le32 dbr;
> +       u8 active;
> +       u8 reserved[3];
> +       __le16 ctrl1;
> +       __le16 ctrl2;
> +       __le32 nbtp;
> +       __le32 dbtp;
> +};
> +
> +struct __packed nct6694_can_information {
> +       u8 tx_fifo_cnt;
> +       u8 rx_fifo_cnt;
> +       u8 reserved[2];
> +       __le32 can_clk;
> +};
> +
> +struct __packed nct6694_can_event {
> +       u8 err;
> +       u8 status;
> +       u8 tx_evt;
> +       u8 rx_evt;
> +       u8 rec;
> +       u8 tec;
> +       u8 reserved[2];
> +};
> +
> +struct __packed nct6694_can_frame {
> +       u8 tag;
> +       u8 flag;
> +       u8 reserved;
> +       u8 length;
> +       __le32 id;
> +       u8 data[64];

Nitpick, use CANFD_MAX_DLEN here:

          u8 data[CANFD_MAX_DLEN];

> +};
> +
> +union __packed nct6694_can_tx {
> +       struct nct6694_can_frame frame;
> +       struct nct6694_can_setting setting;
> +};
> +
> +union __packed nct6694_can_rx {
> +       struct nct6694_can_event event[2];
> +       struct nct6694_can_frame frame;
> +       struct nct6694_can_information info;
> +};
> +
> +struct nct6694_can_priv {
> +       struct can_priv can;    /* must be the first member */
> +       struct can_rx_offload offload;
> +       struct net_device *ndev;
> +       struct nct6694 *nct6694;
> +       struct mutex lock;
> +       struct sk_buff *tx_skb;
> +       struct workqueue_struct *wq;
> +       struct work_struct tx_work;
> +       union nct6694_can_tx *tx;
> +       union nct6694_can_rx *rx;
> +       unsigned char can_idx;
> +};
> +
> +static inline struct nct6694_can_priv *rx_offload_to_priv(struct can_rx_offload *offload)
> +{
> +       return container_of(offload, struct nct6694_can_priv, offload);
> +}
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
> +       .name = DRVNAME,
> +       .tseg1_min = 2,
> +       .tseg1_max = 256,
> +       .tseg2_min = 2,
> +       .tseg2_max = 128,
> +       .sjw_max = 128,
> +       .brp_min = 1,
> +       .brp_max = 511,
> +       .brp_inc = 1,
> +};
> +
> +static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
> +       .name = DRVNAME,
> +       .tseg1_min = 1,
> +       .tseg1_max = 32,
> +       .tseg2_min = 1,
> +       .tseg2_max = 16,
> +       .sjw_max = 16,
> +       .brp_min = 1,
> +       .brp_max = 31,
> +       .brp_inc = 1,
> +};
> +
> +static void nct6694_can_rx_offload(struct can_rx_offload *offload,
> +                                  struct sk_buff *skb)
> +{
> +       struct nct6694_can_priv *priv = rx_offload_to_priv(offload);
> +       int ret;
> +
> +       ret = can_rx_offload_queue_tail(offload, skb);
> +       if (ret)
> +               priv->ndev->stats.rx_fifo_errors++;
> +}
> +
> +static void nct6694_can_handle_lost_msg(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct net_device_stats *stats = &ndev->stats;
> +       struct can_frame *cf;
> +       struct sk_buff *skb;
> +
> +       netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
> +
> +       stats->rx_errors++;
> +       stats->rx_over_errors++;
> +
> +       skb = alloc_can_err_skb(ndev, &cf);
> +       if (!skb)
> +               return;
> +
> +       cf->can_id |= CAN_ERR_CRTL;
> +       cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
> +
> +       nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct nct6694_can_frame *frame = &priv->rx->frame;
> +       struct nct6694_cmd_header cmd_hd = {
> +               .mod = NCT6694_CAN_MOD,
> +               .cmd = NCT6694_CAN_RECEIVE,
> +               .sel = NCT6694_CAN_RECEIVE_SEL(priv->can_idx, 1),
> +               .len = cpu_to_le16(sizeof(*frame))
> +       };
> +       struct canfd_frame *cfd;
> +       struct can_frame *cf;
> +       struct sk_buff *skb;
> +       int ret;
> +
> +       ret = nct6694_read_msg(priv->nct6694, &cmd_hd, frame);
> +       if (ret)
> +               return;
> +
> +       if (frame->flag & NCT6694_CAN_FRAME_FLAG_FD) {

Reduce scope of variable when possible: move declaration of cfd here:

                struct canfd_frame *cfd;

> +               skb = alloc_canfd_skb(priv->ndev, &cfd);
> +               if (!skb)
> +                       return;
> +
> +               cfd->can_id = le32_to_cpu(frame->id);
> +               cfd->len = frame->length;

No. I asked you to sanitize the length in this message:

  https://lore.kernel.org/linux-can/8d66cf66-5564-4272-8c3e-51b715c3d785@wanadoo.fr/

Never use the length as is.

> +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> +                       cfd->can_id |= CAN_EFF_FLAG;
> +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
> +                       cfd->flags |= CANFD_BRS;
> +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_ERR)
> +                       cfd->flags |= CANFD_ESI;
> +
> +               memcpy(cfd->data, frame->data, cfd->len);
> +       } else {

Reduce scope of variable when possible: move declaration of cf here:

                struct canfd_frame *cf;

> +               skb = alloc_can_skb(priv->ndev, &cf);
> +               if (!skb)
> +                       return;
> +
> +               cf->can_id = le32_to_cpu(frame->id);
> +               cf->len = frame->length;

Ditto, sanitize the length.

> +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
> +                       cf->can_id |= CAN_EFF_FLAG;
> +               if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
> +                       cf->can_id |= CAN_RTR_FLAG;
> +
> +               memcpy(cf->data, frame->data, cf->len);

Only copy can data if the frame is not an RTR frame.

                  if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
                          cf->can_id |= CAN_RTR_FLAG;
                  else
                          memcpy(cf->data, frame->data, cf->len);

I already asked you to do this in below comment:

  https://lore.kernel.org/linux-can/a25ea362-142f-4e27-8194-787d9829f607@wanadoo.fr/

> +       }
> +
> +       nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_clean(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> +       if (priv->tx_skb || netif_queue_stopped(ndev))
> +               ndev->stats.tx_errors++;
> +       dev_kfree_skb(priv->tx_skb);
> +       priv->tx_skb = NULL;
> +}
> +
> +static int nct6694_can_get_berr_counter(const struct net_device *ndev,
> +                                       struct can_berr_counter *bec)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct nct6694_can_event *evt = priv->rx->event;
> +       struct nct6694_cmd_header cmd_hd;
> +       u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
> +       int ret;
> +
> +       guard(mutex)(&priv->lock);
> +
> +       cmd_hd = (struct nct6694_cmd_header) {
> +               .mod = NCT6694_CAN_MOD,
> +               .cmd = NCT6694_CAN_EVENT,
> +               .sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask),
> +               .len = cpu_to_le16(sizeof(priv->rx->event))
> +       };
> +
> +       ret = nct6694_read_msg(priv->nct6694, &cmd_hd, evt);
> +       if (ret < 0)
> +               return ret;

You are holding the priv->lock mutex before calling
nct6694_read_msg(). But nct6694_read_msg() then holds the
nct6694->access_lock mutex. Why do you need a double mutex here? What
kind of race scenario are you trying to prevent here?

> +       bec->rxerr = evt[priv->can_idx].rec;
> +       bec->txerr = evt[priv->can_idx].tec;
> +
> +       return 0;
> +}
> +
> +static void nct6694_can_handle_state_change(struct net_device *ndev,
> +                                           u8 status)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       enum can_state new_state = priv->can.state;
> +       enum can_state rx_state, tx_state;
> +       struct can_berr_counter bec;
> +       struct can_frame *cf;
> +       struct sk_buff *skb;
> +
> +       nct6694_can_get_berr_counter(ndev, &bec);
> +       can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state);

Here, you set up tx_state and rx_state...

> +       switch (status) {
> +       case NCT6694_CAN_EVT_STS_ERROR_ACTIVE:
> +               new_state = CAN_STATE_ERROR_ACTIVE;
> +               break;
> +       case NCT6694_CAN_EVT_STS_ERROR_PASSIVE:
> +               new_state = CAN_STATE_ERROR_PASSIVE;
> +               break;
> +       case NCT6694_CAN_EVT_STS_BUS_OFF:
> +               new_state = CAN_STATE_BUS_OFF;
> +               break;
> +       case NCT6694_CAN_EVT_STS_WARNING:
> +               new_state = CAN_STATE_ERROR_WARNING;
> +               break;
> +       default:
> +               netdev_err(ndev, "Receive unknown CAN status event.\n");
> +               return;
> +       }
> +
> +       /* state hasn't changed */
> +       if (new_state == priv->can.state)
> +               return;
> +
> +       skb = alloc_can_err_skb(ndev, &cf);
> +
> +       tx_state = bec.txerr >= bec.rxerr ? new_state : 0;
> +       rx_state = bec.txerr <= bec.rxerr ? new_state : 0;

... but you never used the values returned by
can_state_get_by_berr_counter() and just overwrote the tx and rx
state.

What is the logic here? Why do you need to manually adjust those two
values? Isn't the logic in can_change_state() sufficient?

> +       can_change_state(ndev, cf, tx_state, rx_state);
> +
> +       if (new_state == CAN_STATE_BUS_OFF) {

Same for the new_state. The function can_change_state() calculate the
new state from tx_state and rx_state and save it under
can_priv->state. But here, you do your own calculation.

Only keep one of the two. If your device already tells you the state,
then fine! Just use the information from your device and do not use
can_change_state(). Here, you are doing double work resulting in a
weird mix.

> +               can_bus_off(ndev);
> +       } else if (skb) {
> +               cf->can_id |= CAN_ERR_CNT;
> +               cf->data[6] = bec.txerr;
> +               cf->data[7] = bec.rxerr;
> +       }
> +
> +       nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_handle_bus_err(struct net_device *ndev, u8 bus_err)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct can_frame *cf;
> +       struct sk_buff *skb;
> +
> +       if (bus_err == NCT6694_CAN_EVT_ERR_NO_ERROR)
> +               return;
> +
> +       priv->can.can_stats.bus_error++;
> +
> +       skb = alloc_can_err_skb(ndev, &cf);
> +       if (skb)
> +               cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
> +
> +       switch (bus_err) {
> +       case NCT6694_CAN_EVT_ERR_CRC_ERROR:
> +               netdev_dbg(ndev, "CRC error\n");
> +               ndev->stats.rx_errors++;
> +               if (skb)
> +                       cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
> +               break;
> +
> +       case NCT6694_CAN_EVT_ERR_STUFF_ERROR:
> +               netdev_dbg(ndev, "Stuff error\n");
> +               ndev->stats.rx_errors++;
> +               if (skb)
> +                       cf->data[2] |= CAN_ERR_PROT_STUFF;
> +               break;
> +
> +       case NCT6694_CAN_EVT_ERR_ACK_ERROR:
> +               netdev_dbg(ndev, "Ack error\n");
> +               ndev->stats.tx_errors++;
> +               if (skb) {
> +                       cf->can_id |= CAN_ERR_ACK;
> +                       cf->data[2] |= CAN_ERR_PROT_TX;
> +               }
> +               break;
> +
> +       case NCT6694_CAN_EVT_ERR_FORM_ERROR:
> +               netdev_dbg(ndev, "Form error\n");
> +               ndev->stats.rx_errors++;
> +               if (skb)
> +                       cf->data[2] |= CAN_ERR_PROT_FORM;
> +               break;
> +
> +       case NCT6694_CAN_EVT_ERR_BIT_ERROR:
> +               netdev_dbg(ndev, "Bit error\n");
> +               ndev->stats.tx_errors++;
> +               if (skb)
> +                       cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
> +               break;
> +
> +       default:
> +               break;
> +       }
> +
> +       nct6694_can_rx_offload(&priv->offload, skb);
> +}
> +
> +static void nct6694_can_tx_irq(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct net_device_stats *stats = &ndev->stats;
> +
> +       guard(mutex)(&priv->lock);
> +       stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
> +       stats->tx_packets++;
> +       netif_wake_queue(ndev);
> +}
> +
> +static irqreturn_t nct6694_can_irq(int irq, void *data)
> +{
> +       struct net_device *ndev = data;
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct nct6694_can_event *evt = priv->rx->event;
> +       struct nct6694_cmd_header cmd_hd;
> +       u8 tx_evt, rx_evt, bus_err, can_status;
> +       u8 mask_sts = NCT6694_CAN_EVENT_MASK;

No need for the mask_sts variable. Directly use NCT6694_CAN_EVENT_MASK.

> +       irqreturn_t handled = IRQ_NONE;
> +       int can_idx = priv->can_idx;
> +       int ret;
> +
> +       scoped_guard(mutex, &priv->lock) {

Reduce scope of variable when possible: move the declarations of
cmd_hd and ret here.

> +               cmd_hd = (struct nct6694_cmd_header) {
> +                       .mod = NCT6694_CAN_MOD,
> +                       .cmd = NCT6694_CAN_EVENT,
> +                       .sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask_sts),
> +                       .len = cpu_to_le16(sizeof(priv->rx->event))
> +               };
> +
> +               ret = nct6694_read_msg(priv->nct6694, &cmd_hd, evt);
> +               if (ret < 0)
> +                       return handled;
> +
> +               tx_evt = evt[can_idx].tx_evt;
> +               rx_evt = evt[can_idx].rx_evt;
> +               bus_err = evt[can_idx].err;
> +               can_status = evt[can_idx].status;
> +       }
> +
> +       if (rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) {
> +               nct6694_can_rx(ndev, rx_evt);
> +               handled = IRQ_HANDLED;
> +       }
> +
> +       if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST) {
> +               nct6694_can_handle_lost_msg(ndev);
> +               handled = IRQ_HANDLED;
> +       }
> +
> +       if (can_status) {
> +               nct6694_can_handle_state_change(ndev, can_status);
> +               handled = IRQ_HANDLED;
> +       }
> +
> +       if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
> +               nct6694_handle_bus_err(ndev, bus_err);
> +               handled = IRQ_HANDLED;
> +       }
> +
> +       if (handled)
> +               can_rx_offload_threaded_irq_finish(&priv->offload);
> +
> +       if (tx_evt & NCT6694_CAN_EVT_TX_FIFO_EMPTY)
> +               nct6694_can_tx_irq(ndev);
> +
> +       return handled;
> +}
> +
> +static void nct6694_can_tx(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct nct6694_can_frame *frame = &priv->tx->frame;
> +       struct nct6694_cmd_header cmd_hd = {
> +               .mod = NCT6694_CAN_MOD,
> +               .cmd = NCT6694_CAN_DELIVER,
> +               .sel = NCT6694_CAN_DELIVER_SEL(1),
> +               .len = cpu_to_le16(sizeof(*frame))
> +       };
> +       struct net_device_stats *stats = &ndev->stats;
> +       struct sk_buff *skb = priv->tx_skb;
> +       struct canfd_frame *cfd;
> +       struct can_frame *cf;
> +       u32 txid;
> +       int err;
> +
> +       memset(frame, 0, sizeof(*frame));
> +
> +       if (priv->can_idx == 0)
> +               frame->tag = NCT6694_CAN_FRAME_TAG_CAN0;
> +       else
> +               frame->tag = NCT6694_CAN_FRAME_TAG_CAN1;
> +
> +       if (can_is_canfd_skb(skb)) {

Reduce scope of variable when possible: move declaration of cfd here:

                struct canfd_frame *cfd;

> +               cfd = (struct canfd_frame *)priv->tx_skb->data;
> +
> +               if (cfd->flags & CANFD_BRS)
> +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_BRS;
> +
> +               if (cfd->can_id & CAN_EFF_FLAG) {
> +                       txid = cfd->can_id & CAN_EFF_MASK;
> +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> +               } else {
> +                       txid = cfd->can_id & CAN_SFF_MASK;
> +               }
> +               frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
> +               frame->id = cpu_to_le32(txid);
> +               frame->length = cfd->len;
> +
> +               memcpy(frame->data, cfd->data, cfd->len);
> +       } else {

Reduce scope of variable when possible: move declaration of cf here:

                struct canfd_frame *cf;

> +               cf = (struct can_frame *)priv->tx_skb->data;
> +
> +               if (cf->can_id & CAN_RTR_FLAG)
> +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_RTR;
> +
> +               if (cf->can_id & CAN_EFF_FLAG) {
> +                       txid = cf->can_id & CAN_EFF_MASK;
> +                       frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
> +               } else {
> +                       txid = cf->can_id & CAN_SFF_MASK;
> +               }
> +               frame->id = cpu_to_le32(txid);
> +               frame->length = cf->len;
> +
> +               memcpy(frame->data, cf->data, cf->len);

Don't copy cf->data if the can frame is a RTR frame.

> +       }
> +
> +       err = nct6694_write_msg(priv->nct6694, &cmd_hd, frame);
> +       if (err) {
> +               netdev_err(ndev, "%s: Tx FIFO full!\n", __func__);
> +               can_free_echo_skb(ndev, 0, NULL);
> +               stats->tx_dropped++;
> +               stats->tx_errors++;
> +               netif_wake_queue(ndev);
> +       }
> +}
> +
> +static void nct6694_can_tx_work(struct work_struct *work)
> +{
> +       struct nct6694_can_priv *priv = container_of(work,
> +                                                    struct nct6694_can_priv,
> +                                                    tx_work);
> +       struct net_device *ndev = priv->ndev;
> +
> +       guard(mutex)(&priv->lock);
> +
> +       if (priv->tx_skb) {
> +               if (priv->can.state == CAN_STATE_BUS_OFF) {
> +                       nct6694_can_clean(ndev);
> +               } else {
> +                       nct6694_can_tx(ndev);
> +                       can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
> +                       priv->tx_skb = NULL;
> +               }
> +       }
> +}
> +
> +static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
> +                                         struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> +       if (can_dev_dropped_skb(ndev, skb))
> +               return NETDEV_TX_OK;
> +
> +       if (priv->tx_skb) {
> +               netdev_err(ndev, "hard_xmit called while tx busy\n");
> +               return NETDEV_TX_BUSY;
> +       }
> +
> +       netif_stop_queue(ndev);
> +       priv->tx_skb = skb;
> +       queue_work(priv->wq, &priv->tx_work);
> +
> +       return NETDEV_TX_OK;
> +}
> +
> +static int nct6694_can_start(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       struct nct6694_can_setting *setting = &priv->tx->setting;
> +       struct nct6694_cmd_header cmd_hd = {
> +               .mod = NCT6694_CAN_MOD,
> +               .cmd = NCT6694_CAN_SETTING,
> +               .sel = NCT6694_CAN_SETTING_SEL(priv->can_idx),
> +               .len = cpu_to_le16(sizeof(*setting))
> +       };
> +       const struct can_bittiming *n_bt = &priv->can.bittiming;
> +       const struct can_bittiming *d_bt = &priv->can.data_bittiming;
> +       int ret;
> +
> +       guard(mutex)(&priv->lock);
> +
> +       memset(setting, 0, sizeof(*setting));
> +       setting->nbr = cpu_to_le32(n_bt->bitrate);
> +       setting->dbr = cpu_to_le32(d_bt->bitrate);
> +
> +       if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
> +               setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
> +
> +       if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
> +           priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
> +               setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
> +
> +       if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
> +               setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
> +
> +       ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
> +       if (ret)
> +               return ret;
> +
> +       priv->can.state = CAN_STATE_ERROR_ACTIVE;
> +
> +       return ret;
> +}
> +
> +static int nct6694_can_stop(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +
> +       netif_stop_queue(ndev);
> +       free_irq(ndev->irq, ndev);
> +       destroy_workqueue(priv->wq);
> +       priv->wq = NULL;
> +       nct6694_can_clean(ndev);
> +       priv->can.state = CAN_STATE_STOPPED;
> +       can_rx_offload_disable(&priv->offload);
> +       close_candev(ndev);
> +
> +       return 0;
> +}
> +
> +static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
> +{
> +       switch (mode) {
> +       case CAN_MODE_START:
> +               nct6694_can_clean(ndev);
> +               nct6694_can_start(ndev);
> +               netif_wake_queue(ndev);
> +               break;
> +       default:
> +               return -EOPNOTSUPP;
> +       }
> +
> +       return 0;
> +}
> +
> +static int nct6694_can_open(struct net_device *ndev)
> +{
> +       struct nct6694_can_priv *priv = netdev_priv(ndev);
> +       int ret;
> +
> +       ret = open_candev(ndev);
> +       if (ret)
> +               return ret;
> +
> +       can_rx_offload_enable(&priv->offload);
> +
> +       ret = request_threaded_irq(ndev->irq, NULL,
> +                                  nct6694_can_irq, IRQF_ONESHOT,
> +                                  "nct6694_can", ndev);
> +       if (ret) {
> +               netdev_err(ndev, "Failed to request IRQ\n");
> +               goto close_candev;
> +       }
> +
> +       priv->wq = alloc_ordered_workqueue("%s-nct6694_wq",
> +                                          WQ_FREEZABLE | WQ_MEM_RECLAIM,
> +                                          ndev->name);
> +       if (!priv->wq) {
> +               ret = -ENOMEM;
> +               goto free_irq;
> +       }
> +
> +       priv->tx_skb = NULL;
> +
> +       ret = nct6694_can_start(ndev);
> +       if (ret)
> +               goto destroy_wq;
> +
> +       netif_start_queue(ndev);
> +
> +       return 0;
> +
> +destroy_wq:
> +       destroy_workqueue(priv->wq);
> +free_irq:
> +       free_irq(ndev->irq, ndev);
> +close_candev:
> +       can_rx_offload_disable(&priv->offload);
> +       close_candev(ndev);
> +       return ret;
> +}
> +
> +static const struct net_device_ops nct6694_can_netdev_ops = {
> +       .ndo_open = nct6694_can_open,
> +       .ndo_stop = nct6694_can_stop,
> +       .ndo_start_xmit = nct6694_can_start_xmit,
> +       .ndo_change_mtu = can_change_mtu,
> +};
> +
> +static const struct ethtool_ops nct6694_can_ethtool_ops = {
> +       .get_ts_info = ethtool_op_get_ts_info,
> +};
> +
> +static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
> +{
> +       struct nct6694_can_information *info = &priv->rx->info;
> +       struct nct6694_cmd_header cmd_hd = {
> +               .mod = NCT6694_CAN_MOD,
> +               .cmd = NCT6694_CAN_INFORMATION,
> +               .sel = NCT6694_CAN_INFORMATION_SEL,
> +               .len = cpu_to_le16(sizeof(*info))
> +       };
> +       int ret;
> +
> +       ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
> +       if (ret)
> +               return ret;
> +
> +       return le32_to_cpu(info->can_clk);
> +}
> +
> +static int nct6694_can_probe(struct platform_device *pdev)
> +{
> +       const struct mfd_cell *cell = mfd_get_cell(pdev);
> +       struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
> +       struct nct6694_can_priv *priv;
> +       struct net_device *ndev;
> +       int ret, irq, can_clk;
> +
> +       irq = irq_create_mapping(nct6694->domain,
> +                                NCT6694_IRQ_CAN1 + cell->id);
> +       if (!irq)
> +               return irq;
> +
> +       ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
> +       if (!ndev)
> +               return -ENOMEM;
> +
> +       ndev->irq = irq;
> +       ndev->flags |= IFF_ECHO;
> +       ndev->netdev_ops = &nct6694_can_netdev_ops;
> +       ndev->ethtool_ops = &nct6694_can_ethtool_ops;

Your device has two CAN interfaces, right? Do not forget to populate
netdev->dev_port.

          netdev->dev_port = cell->id;

> +       priv = netdev_priv(ndev);
> +       priv->nct6694 = nct6694;
> +       priv->ndev = ndev;
> +
> +       priv->tx = devm_kzalloc(&pdev->dev, sizeof(union nct6694_can_tx),
> +                               GFP_KERNEL);
> +       if (!priv->tx) {
> +               ret = -ENOMEM;
> +               goto free_candev;
> +       }
> +
> +       priv->rx = devm_kzalloc(&pdev->dev, sizeof(union nct6694_can_rx),
> +                               GFP_KERNEL);
> +       if (!priv->rx) {
> +               ret = -ENOMEM;
> +               goto free_candev;
> +       }
> +
> +       can_clk = nct6694_can_get_clock(priv);
> +       if (can_clk < 0) {
> +               ret = dev_err_probe(&pdev->dev, can_clk,
> +                                   "Failed to get clock\n");
> +               goto free_candev;
> +       }
> +
> +       devm_mutex_init(&pdev->dev, &priv->lock);
> +       INIT_WORK(&priv->tx_work, nct6694_can_tx_work);
> +
> +       priv->can_idx = cell->id;
> +       priv->can.state = CAN_STATE_STOPPED;
> +       priv->can.clock.freq = can_clk;
> +       priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
> +       priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
> +       priv->can.do_set_mode = nct6694_can_set_mode;
> +       priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
> +
> +       priv->can.ctrlmode = CAN_CTRLMODE_FD;
> +
> +       priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK            |
> +                                      CAN_CTRLMODE_LISTENONLY          |
> +                                      CAN_CTRLMODE_FD                  |
> +                                      CAN_CTRLMODE_FD_NON_ISO          |
> +                                      CAN_CTRLMODE_BERR_REPORTING;
> +
> +       ret = can_rx_offload_add_manual(ndev, &priv->offload,
> +                                       NCT6694_NAPI_WEIGHT);
> +       if (ret) {
> +               dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
> +               goto free_candev;
> +       }
> +
> +       platform_set_drvdata(pdev, priv);
> +       SET_NETDEV_DEV(priv->ndev, &pdev->dev);
> +
> +       ret = register_candev(priv->ndev);
> +       if (ret)
> +               goto del_rx_offload;
> +
> +       return 0;
> +
> +del_rx_offload:
> +       can_rx_offload_del(&priv->offload);
> +free_candev:
> +       free_candev(ndev);
> +       return ret;
> +}
> +
> +static void nct6694_can_remove(struct platform_device *pdev)
> +{
> +       struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
> +
> +       cancel_work_sync(&priv->tx_work);
> +       unregister_candev(priv->ndev);
> +       can_rx_offload_del(&priv->offload);
> +       free_candev(priv->ndev);
> +}
> +
> +static struct platform_driver nct6694_can_driver = {
> +       .driver = {
> +               .name   = DRVNAME,
> +       },
> +       .probe          = nct6694_can_probe,
> +       .remove         = nct6694_can_remove,
> +};
> +
> +module_platform_driver(nct6694_can_driver);
> +
> +MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
> +MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:nct6694-can");


Yours sincerely,
Vincent Mailhol

^ permalink raw reply

* Re: [PATCH net-next v8 06/11] net: ipv6: Use link netns in newlink() of rtnl_link_ops
From: Kuniyuki Iwashima @ 2025-01-14  4:49 UTC (permalink / raw)
  To: shaw.leon
  Cc: alex.aring, andrew+netdev, b.a.t.m.a.n, bpf, bridge, davem,
	donald.hunter, dsahern, edumazet, herbert, horms, kuba, kuniyu,
	linux-can, linux-kernel, linux-kselftest, linux-ppp, linux-rdma,
	linux-wireless, linux-wpan, miquel.raynal, netdev,
	osmocom-net-gprs, pabeni, shuah, stefan, steffen.klassert,
	wireguard
In-Reply-To: <20250113143719.7948-3-shaw.leon@gmail.com>

From: Xiao Liang <shaw.leon@gmail.com>
Date: Mon, 13 Jan 2025 22:37:14 +0800
> diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c
> index 2a6a424806aa..ac5e402c34bc 100644
> --- a/drivers/net/bonding/bond_netlink.c
> +++ b/drivers/net/bonding/bond_netlink.c
> @@ -564,10 +564,12 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[],
>  	return 0;
>  }
>  
> -static int bond_newlink(struct net *src_net, struct net_device *bond_dev,
> -			struct nlattr *tb[], struct nlattr *data[],
> +static int bond_newlink(struct net_device *bond_dev,
> +			struct rtnl_newlink_params *params,
>  			struct netlink_ext_ack *extack)
>  {
> +	struct nlattr **data = params->data;
> +	struct nlattr **tb = params->tb;
>  	int err;
>  
>  	err = bond_changelink(bond_dev, tb, data, extack);

Note that IFLA_BOND_ACTIVE_SLAVE uses dev_net(dev) for
__dev_get_by_index().


[...]
> diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c
> index fed4fe2a4748..0c496aa1f706 100644
> --- a/drivers/net/macvlan.c
> +++ b/drivers/net/macvlan.c
> @@ -1565,11 +1565,12 @@ int macvlan_common_newlink(struct net *src_net, struct net_device *dev,
>  }
>  EXPORT_SYMBOL_GPL(macvlan_common_newlink);
>  
> -static int macvlan_newlink(struct net *src_net, struct net_device *dev,
> -			   struct nlattr *tb[], struct nlattr *data[],
> +static int macvlan_newlink(struct net_device *dev,
> +			   struct rtnl_newlink_params *params,
>  			   struct netlink_ext_ack *extack)
>  {
> -	return macvlan_common_newlink(src_net, dev, tb, data, extack);
> +	return macvlan_common_newlink(params->net, dev, params->tb,
> +				      params->data, extack);

Pass params as is as you did for ipvlan_link_new().

Same for macvtap_newlink().


[...]
> diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c
> index 1e1b00756be7..1e9eadc77da2 100644
> --- a/drivers/net/netkit.c
> +++ b/drivers/net/netkit.c
> @@ -327,10 +327,13 @@ static int netkit_validate(struct nlattr *tb[], struct nlattr *data[],
>  
>  static struct rtnl_link_ops netkit_link_ops;
>  
> -static int netkit_new_link(struct net *peer_net, struct net_device *dev,
> -			   struct nlattr *tb[], struct nlattr *data[],
> +static int netkit_new_link(struct net_device *dev,
> +			   struct rtnl_newlink_params *params,
>  			   struct netlink_ext_ack *extack)
>  {
> +	struct nlattr **data = params->data;
> +	struct net *peer_net = params->net;
> +	struct nlattr **tb = params->tb;

nit: please keep the reverse xmas tree order.


>  	struct nlattr *peer_tb[IFLA_MAX + 1], **tbp = tb, *attr;

you can define *tbp here and initialise it later.

  	struct nlattr *peer_tb[IFLA_MAX + 1], **tbp, *attr;

>  	enum netkit_action policy_prim = NETKIT_PASS;
>  	enum netkit_action policy_peer = NETKIT_PASS;


[...]
> @@ -1064,6 +1067,11 @@ static void wwan_create_default_link(struct wwan_device *wwandev,
>  	struct net_device *dev;
>  	struct nlmsghdr *nlh;
>  	struct sk_buff *msg;
> +	struct rtnl_newlink_params params = {
> +		.net = &init_net,
> +		.tb = tb,
> +		.data = data,
> +	};

nit: Reverse xmas tree order


[...]
> diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
> index ec98349b9620..7ff5e96f6ba7 100644
> --- a/net/core/rtnetlink.c
> +++ b/net/core/rtnetlink.c
> @@ -3766,6 +3766,14 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
>  	struct net_device *dev;
>  	char ifname[IFNAMSIZ];
>  	int err;
> +	struct rtnl_newlink_params params = {

nit: Reverse xmas tree order


> +		.net = net,

Use sock_net(skb->sk) directly here and remove net defined above,
which is no longer used in this function.

---8<---
        unsigned char name_assign_type = NET_NAME_USER;
        struct rtnl_newlink_params params = {
                .net = sock_net(skb->sk),
                .src_net = net,
                .link_net = link_net,
                .peer_net = peer_net,
                .tb = tb,
                .data = data,
        };
        u32 portid = NETLINK_CB(skb).portid;
---8<---


[...]
> @@ -1698,6 +1702,10 @@ struct net_device *gretap_fb_dev_create(struct net *net, const char *name,
>  	LIST_HEAD(list_kill);
>  	struct ip_tunnel *t;
>  	int err;
> +	struct rtnl_newlink_params params = {
> +		.net = net,
> +		.tb = tb,
> +	};
>  
>  	memset(&tb, 0, sizeof(tb));

nit: Reverse xmas tree

^ permalink raw reply

* [PATCH v5 7/7] rtc: Add Nuvoton NCT6694 RTC support
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

This driver supports RTC functionality for NCT6694 MFD device
based on USB interface.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS               |   1 +
 drivers/rtc/Kconfig       |  10 ++
 drivers/rtc/Makefile      |   1 +
 drivers/rtc/rtc-nct6694.c | 286 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 298 insertions(+)
 create mode 100644 drivers/rtc/rtc-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a14a2d65b8a6..5275ff7a793c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16726,6 +16726,7 @@ F:	drivers/hwmon/nct6694-hwmon.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/usb/nct6694_canfd.c
+F:	drivers/rtc/rtc-nct6694.c
 F:	drivers/watchdog/nct6694_wdt.c
 F:	include/linux/mfd/nct6694.h
 
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index a60bcc791a48..aeab67acbc84 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -416,6 +416,16 @@ config RTC_DRV_NCT3018Y
 	   This driver can also be built as a module, if so, the module will be
 	   called "rtc-nct3018y".
 
+config RTC_DRV_NCT6694
+	tristate "Nuvoton NCT6694 RTC support"
+	depends on MFD_NCT6694
+	help
+	  If you say yes to this option, support will be included for Nuvoton
+	  NCT6694, a USB device to RTC.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called rtc-nct6694.
+
 config RTC_DRV_RK808
 	tristate "Rockchip RK805/RK808/RK809/RK817/RK818 RTC"
 	depends on MFD_RK8XX
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 489b4ab07068..d0d6f4a4972e 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -118,6 +118,7 @@ obj-$(CONFIG_RTC_DRV_MXC)	+= rtc-mxc.o
 obj-$(CONFIG_RTC_DRV_MXC_V2)	+= rtc-mxc_v2.o
 obj-$(CONFIG_RTC_DRV_GAMECUBE)	+= rtc-gamecube.o
 obj-$(CONFIG_RTC_DRV_NCT3018Y)	+= rtc-nct3018y.o
+obj-$(CONFIG_RTC_DRV_NCT6694)	+= rtc-nct6694.o
 obj-$(CONFIG_RTC_DRV_NTXEC)	+= rtc-ntxec.o
 obj-$(CONFIG_RTC_DRV_OMAP)	+= rtc-omap.o
 obj-$(CONFIG_RTC_DRV_OPAL)	+= rtc-opal.o
diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c
new file mode 100644
index 000000000000..dbb5ccae2796
--- /dev/null
+++ b/drivers/rtc/rtc-nct6694.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 RTC driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bcd.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 RTC controller.
+ * This defines the module type used for communication with the NCT6694
+ * RTC controller over the USB interface.
+ */
+#define NCT6694_RTC_MOD		0x08
+
+/* Command 00h - RTC Time */
+#define NCT6694_RTC_TIME	0x0000
+#define NCT6694_RTC_TIME_SEL	0x00
+
+/* Command 01h - RTC Alarm */
+#define NCT6694_RTC_ALARM	0x01
+#define NCT6694_RTC_ALARM_SEL	0x00
+
+/* Command 02h - RTC Status */
+#define NCT6694_RTC_STATUS	0x02
+#define NCT6694_RTC_STATUS_SEL	0x00
+
+#define NCT6694_RTC_IRQ_INT_EN	BIT(0)	/* Transmit a USB INT-in when RTC alarm */
+#define NCT6694_RTC_IRQ_GPO_EN	BIT(5)	/* Trigger a GPO Low Pulse when RTC alarm */
+
+#define NCT6694_RTC_IRQ_EN	(NCT6694_RTC_IRQ_INT_EN | NCT6694_RTC_IRQ_GPO_EN)
+#define NCT6694_RTC_IRQ_STS	BIT(0)	/* Write 1 clear IRQ status */
+
+struct __packed nct6694_rtc_time {
+	u8 sec;
+	u8 min;
+	u8 hour;
+	u8 week;
+	u8 day;
+	u8 month;
+	u8 year;
+};
+
+struct __packed nct6694_rtc_alarm {
+	u8 sec;
+	u8 min;
+	u8 hour;
+	u8 alarm_en;
+	u8 alarm_pend;
+};
+
+struct __packed nct6694_rtc_status {
+	u8 irq_en;
+	u8 irq_pend;
+};
+
+union __packed nct6694_rtc_msg {
+	struct nct6694_rtc_time time;
+	struct nct6694_rtc_alarm alarm;
+	struct nct6694_rtc_status sts;
+};
+
+struct nct6694_rtc_data {
+	struct nct6694 *nct6694;
+	struct rtc_device *rtc;
+	union nct6694_rtc_msg *msg;
+};
+
+static int nct6694_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_time *time = &data->msg->time;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_TIME,
+		.sel = NCT6694_RTC_TIME_SEL,
+		.len = cpu_to_le16(sizeof(*time))
+	};
+	int ret;
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, time);
+	if (ret)
+		return ret;
+
+	tm->tm_sec = bcd2bin(time->sec);		/* tm_sec expect 0 ~ 59 */
+	tm->tm_min = bcd2bin(time->min);		/* tm_min expect 0 ~ 59 */
+	tm->tm_hour = bcd2bin(time->hour);		/* tm_hour expect 0 ~ 23 */
+	tm->tm_wday = bcd2bin(time->week) - 1;		/* tm_wday expect 0 ~ 6 */
+	tm->tm_mday = bcd2bin(time->day);		/* tm_mday expect 1 ~ 31 */
+	tm->tm_mon = bcd2bin(time->month) - 1;		/* tm_month expect 0 ~ 11 */
+	tm->tm_year = bcd2bin(time->year) + 100;	/* tm_year expect since 1900 */
+
+	return ret;
+}
+
+static int nct6694_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_time *time = &data->msg->time;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_TIME,
+		.sel = NCT6694_RTC_TIME_SEL,
+		.len = cpu_to_le16(sizeof(*time))
+	};
+
+	time->sec = bin2bcd(tm->tm_sec);
+	time->min = bin2bcd(tm->tm_min);
+	time->hour = bin2bcd(tm->tm_hour);
+	time->week = bin2bcd(tm->tm_wday + 1);
+	time->day = bin2bcd(tm->tm_mday);
+	time->month = bin2bcd(tm->tm_mon + 1);
+	time->year = bin2bcd(tm->tm_year - 100);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, time);
+}
+
+static int nct6694_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_alarm *alarm = &data->msg->alarm;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_ALARM,
+		.sel = NCT6694_RTC_ALARM_SEL,
+		.len = cpu_to_le16(sizeof(*alarm))
+	};
+	int ret;
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, alarm);
+	if (ret)
+		return ret;
+
+	alrm->time.tm_sec = bcd2bin(alarm->sec);
+	alrm->time.tm_min = bcd2bin(alarm->min);
+	alrm->time.tm_hour = bcd2bin(alarm->hour);
+	alrm->enabled = alarm->alarm_en;
+	alrm->pending = alarm->alarm_pend;
+
+	return ret;
+}
+
+static int nct6694_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_alarm *alarm = &data->msg->alarm;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_ALARM,
+		.sel = NCT6694_RTC_ALARM_SEL,
+		.len = cpu_to_le16(sizeof(*alarm))
+	};
+
+	alarm->sec = bin2bcd(alrm->time.tm_sec);
+	alarm->min = bin2bcd(alrm->time.tm_min);
+	alarm->hour = bin2bcd(alrm->time.tm_hour);
+	alarm->alarm_en = alrm->enabled ? NCT6694_RTC_IRQ_EN : 0;
+	alarm->alarm_pend = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, alarm);
+}
+
+static int nct6694_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct nct6694_rtc_data *data = dev_get_drvdata(dev);
+	struct nct6694_rtc_status *sts = &data->msg->sts;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_STATUS,
+		.sel = NCT6694_RTC_STATUS_SEL,
+		.len = cpu_to_le16(sizeof(*sts))
+	};
+
+	if (enabled)
+		sts->irq_en |= NCT6694_RTC_IRQ_EN;
+	else
+		sts->irq_en &= ~NCT6694_RTC_IRQ_EN;
+
+	sts->irq_pend = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, sts);
+}
+
+static const struct rtc_class_ops nct6694_rtc_ops = {
+	.read_time = nct6694_rtc_read_time,
+	.set_time = nct6694_rtc_set_time,
+	.read_alarm = nct6694_rtc_read_alarm,
+	.set_alarm = nct6694_rtc_set_alarm,
+	.alarm_irq_enable = nct6694_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t nct6694_irq(int irq, void *dev_id)
+{
+	struct nct6694_rtc_data *data = dev_id;
+	struct nct6694_rtc_status *sts = &data->msg->sts;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_RTC_MOD,
+		.cmd = NCT6694_RTC_STATUS,
+		.sel = NCT6694_RTC_STATUS_SEL,
+		.len = cpu_to_le16(sizeof(*sts))
+	};
+	int ret;
+
+	rtc_lock(data->rtc);
+
+	sts->irq_en = NCT6694_RTC_IRQ_EN;
+	sts->irq_pend = NCT6694_RTC_IRQ_STS;
+	ret = nct6694_write_msg(data->nct6694, &cmd_hd, sts);
+	if (ret) {
+		rtc_unlock(data->rtc);
+		return IRQ_NONE;
+	}
+
+	rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF);
+
+	rtc_unlock(data->rtc);
+
+	return IRQ_HANDLED;
+}
+
+static int nct6694_rtc_probe(struct platform_device *pdev)
+{
+	struct nct6694_rtc_data *data;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	int ret, irq;
+
+	irq = irq_create_mapping(nct6694->domain, NCT6694_IRQ_RTC);
+	if (!irq)
+		return -EINVAL;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_rtc_msg),
+				 GFP_KERNEL);
+	if (!data->msg)
+		return -ENOMEM;
+
+	data->rtc = devm_rtc_allocate_device(&pdev->dev);
+	if (IS_ERR(data->rtc))
+		return PTR_ERR(data->rtc);
+
+	data->nct6694 = nct6694;
+	data->rtc->ops = &nct6694_rtc_ops;
+	data->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+	data->rtc->range_max = RTC_TIMESTAMP_END_2099;
+
+	platform_set_drvdata(pdev, data);
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+					nct6694_irq, IRQF_ONESHOT,
+					"nct6694-rtc", data);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n");
+
+	ret = devm_rtc_register_device(data->rtc);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to register rtc\n");
+
+	device_init_wakeup(&pdev->dev, true);
+	return 0;
+}
+
+static struct platform_driver nct6694_rtc_driver = {
+	.driver = {
+		.name	= "nct6694-rtc",
+	},
+	.probe		= nct6694_rtc_probe,
+};
+
+module_platform_driver(nct6694_rtc_driver);
+
+MODULE_DESCRIPTION("USB-RTC driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-rtc");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 6/7] hwmon: Add Nuvoton NCT6694 HWMON support
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

This driver supports Hardware monitor functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS                   |   1 +
 drivers/hwmon/Kconfig         |  10 +
 drivers/hwmon/Makefile        |   1 +
 drivers/hwmon/nct6694-hwmon.c | 947 ++++++++++++++++++++++++++++++++++
 4 files changed, 959 insertions(+)
 create mode 100644 drivers/hwmon/nct6694-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a772747d493f..a14a2d65b8a6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16722,6 +16722,7 @@ M:	Ming Yu <tmyu0@nuvoton.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
+F:	drivers/hwmon/nct6694-hwmon.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/usb/nct6694_canfd.c
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index dd376602f3f1..df40986424bd 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1636,6 +1636,16 @@ config SENSORS_NCT6683
 	  This driver can also be built as a module. If so, the module
 	  will be called nct6683.
 
+config SENSORS_NCT6694
+	tristate "Nuvoton NCT6694 Hardware Monitor support"
+	depends on MFD_NCT6694
+	help
+	  Say Y here to support Nuvoton NCT6694 hardware monitoring
+	  functionality.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nct6694-hwmon.
+
 config SENSORS_NCT6775_CORE
 	tristate
 	select REGMAP
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b827b92f2a78..27a43e67cdb7 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -168,6 +168,7 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
 obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o
 obj-$(CONFIG_SENSORS_MR75203)	+= mr75203.o
 obj-$(CONFIG_SENSORS_NCT6683)	+= nct6683.o
+obj-$(CONFIG_SENSORS_NCT6694)	+= nct6694-hwmon.o
 obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o
 nct6775-objs			:= nct6775-platform.o
 obj-$(CONFIG_SENSORS_NCT6775)	+= nct6775.o
diff --git a/drivers/hwmon/nct6694-hwmon.c b/drivers/hwmon/nct6694-hwmon.c
new file mode 100644
index 000000000000..b2f115b5d8eb
--- /dev/null
+++ b/drivers/hwmon/nct6694-hwmon.c
@@ -0,0 +1,947 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 HWMON driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/hwmon.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+/*
+ * USB command module type for NCT6694 report channel
+ * This defines the module type used for communication with the NCT6694
+ * report channel over the USB interface.
+ */
+#define NCT6694_RPT_MOD			0xFF
+
+/* Report channel */
+/*
+ * The report channel is used to report the status of the hardware monitor
+ * devices, such as voltage, temperature, fan speed, and PWM.
+ */
+#define NCT6694_VIN_IDX(x)		(0x00 + (x))
+#define NCT6694_TIN_IDX(x)			\
+	({ typeof(x) (_x) = (x);		\
+	 ((_x) < 10) ? (0x10 + ((_x) * 2)) :	\
+	 (0x30 + (((_x) - 10) * 2)); })
+#define NCT6694_FIN_IDX(x)		(0x50 + ((x) * 2))
+#define NCT6694_PWM_IDX(x)		(0x70 + (x))
+#define NCT6694_VIN_STS(x)		(0x68 + (x))
+#define NCT6694_TIN_STS(x)		(0x6A + (x))
+#define NCT6694_FIN_STS(x)		(0x6E + (x))
+
+/*
+ * USB command module type for NCT6694 HWMON controller.
+ * This defines the module type used for communication with the NCT6694
+ * HWMON controller over the USB interface.
+ */
+#define NCT6694_HWMON_MOD		0x00
+
+/* Command 00h - Hardware Monitor Control */
+#define NCT6694_HWMON_CONTROL		0x00
+#define NCT6694_HWMON_CONTROL_SEL	0x00
+
+/* Command 02h - Alarm Control */
+#define NCT6694_HWMON_ALARM		0x02
+#define NCT6694_HWMON_ALARM_SEL		0x00
+
+/*
+ * USB command module type for NCT6694 PWM controller.
+ * This defines the module type used for communication with the NCT6694
+ * PWM controller over the USB interface.
+ */
+#define NCT6694_PWM_MOD			0x01
+
+/* PWM Command - Manual Control */
+#define NCT6694_PWM_CONTROL		0x01
+#define NCT6694_PWM_CONTROL_SEL		0x00
+
+#define NCT6694_FREQ_FROM_REG(reg)	((reg) * 25000 / 255)
+#define NCT6694_FREQ_TO_REG(val)	\
+	(DIV_ROUND_CLOSEST(clamp_val((val), 100, 25000) * 255, 25000))
+
+#define NCT6694_LSB_REG_MASK		GENMASK(7, 5)
+#define NCT6694_TIN_HYST_MASK		GENMASK(7, 5)
+
+enum nct6694_hwmon_temp_mode {
+	NCT6694_HWMON_TWOTIME_IRQ = 0,
+	NCT6694_HWMON_ONETIME_IRQ,
+	NCT6694_HWMON_REALTIME_IRQ,
+	NCT6694_HWMON_COMPARE_IRQ,
+};
+
+struct __packed nct6694_hwmon_control {
+	u8 vin_en[2];
+	u8 tin_en[2];
+	u8 fin_en[2];
+	u8 pwm_en[2];
+	u8 reserved1[40];
+	u8 pwm_freq[10];
+	u8 reserved2[6];
+};
+
+struct __packed nct6694_hwmon_alarm {
+	u8 smi_ctrl;
+	u8 reserved1[15];
+	struct {
+		u8 hl;
+		u8 ll;
+	} vin_limit[16];
+	struct {
+		u8 hyst;
+		s8 hl;
+	} tin_cfg[32];
+	__be16 fin_ll[10];
+	u8 reserved2[4];
+};
+
+struct __packed nct6694_pwm_control {
+	u8 mal_en[2];
+	u8 mal_val[10];
+	u8 reserved[12];
+};
+
+union __packed nct6694_hwmon_rpt {
+	u8 vin;
+	struct {
+		u8 msb;
+		u8 lsb;
+	} tin;
+	__be16 fin;
+	u8 pwm;
+	u8 status;
+};
+
+union __packed nct6694_hwmon_msg {
+	struct nct6694_hwmon_alarm hwmon_alarm;
+	struct nct6694_pwm_control pwm_ctrl;
+};
+
+struct nct6694_hwmon_data {
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	struct nct6694_hwmon_control hwmon_en;
+	union nct6694_hwmon_rpt *rpt;
+	union nct6694_hwmon_msg *msg;
+};
+
+static inline long in_from_reg(u8 reg)
+{
+	return reg * 16;
+}
+
+static inline u8 in_to_reg(long val)
+{
+	return DIV_ROUND_CLOSEST(val, 16);
+}
+
+static inline long temp_from_reg(s8 reg)
+{
+	return reg * 1000;
+}
+
+static inline s8 temp_to_reg(long val)
+{
+	return DIV_ROUND_CLOSEST(val, 1000);
+}
+
+#define NCT6694_HWMON_IN_CONFIG (HWMON_I_INPUT | HWMON_I_ENABLE |	\
+				 HWMON_I_MAX | HWMON_I_MIN |		\
+				 HWMON_I_ALARM)
+#define NCT6694_HWMON_TEMP_CONFIG (HWMON_T_INPUT | HWMON_T_ENABLE |	\
+				   HWMON_T_MAX | HWMON_T_MAX_HYST |	\
+				   HWMON_T_MAX_ALARM)
+#define NCT6694_HWMON_FAN_CONFIG (HWMON_F_INPUT | HWMON_F_ENABLE |	\
+				  HWMON_F_MIN | HWMON_F_MIN_ALARM)
+#define NCT6694_HWMON_PWM_CONFIG (HWMON_PWM_INPUT | HWMON_PWM_ENABLE |	\
+				  HWMON_PWM_FREQ)
+static const struct hwmon_channel_info *nct6694_info[] = {
+	HWMON_CHANNEL_INFO(in,
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN0 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN1 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN2 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN3 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN5 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN6 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN7 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN14 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN15 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VIN16 */
+			   NCT6694_HWMON_IN_CONFIG,	/* VBAT */
+			   NCT6694_HWMON_IN_CONFIG,	/* VSB */
+			   NCT6694_HWMON_IN_CONFIG,	/* AVSB */
+			   NCT6694_HWMON_IN_CONFIG,	/* VCC */
+			   NCT6694_HWMON_IN_CONFIG,	/* VHIF */
+			   NCT6694_HWMON_IN_CONFIG),	/* VTT */
+
+	HWMON_CHANNEL_INFO(temp,
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR1 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR2 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR14 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR15 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* THR16 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP0 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP1 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP2 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP3 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* TDP4 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN0 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN1 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN2 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN3 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN4 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN5 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN6 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN7 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN8 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN9 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN10 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN11 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN12 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN13 */
+			   NCT6694_HWMON_TEMP_CONFIG,	/* DTIN14 */
+			   NCT6694_HWMON_TEMP_CONFIG),	/* DTIN15 */
+
+	HWMON_CHANNEL_INFO(fan,
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN0 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN1 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN2 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN3 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN4 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN5 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN6 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN7 */
+			   NCT6694_HWMON_FAN_CONFIG,	/* FIN8 */
+			   NCT6694_HWMON_FAN_CONFIG),	/* FIN9 */
+
+	HWMON_CHANNEL_INFO(pwm,
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM0 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM1 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM2 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM3 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM4 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM5 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM6 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM7 */
+			   NCT6694_HWMON_PWM_CONFIG,	/* PWM8 */
+			   NCT6694_HWMON_PWM_CONFIG),	/* PWM9 */
+	NULL
+};
+
+static int nct6694_in_read(struct device *dev, u32 attr, int channel,
+			   long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char vin_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_in_enable:
+		vin_en = data->hwmon_en.vin_en[(channel / 8)];
+		*val = !!(vin_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_in_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_VIN_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->vin))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->vin);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->rpt->vin);
+
+		return 0;
+	case hwmon_in_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].hl);
+
+		return 0;
+	case hwmon_in_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = in_from_reg(data->msg->hwmon_alarm.vin_limit[channel].ll);
+
+		return 0;
+	case hwmon_in_alarm:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_VIN_STS(channel / 8)),
+			.len = cpu_to_le16(sizeof(data->rpt->status))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->status);
+		if (ret)
+			return ret;
+
+		*val = !!(data->rpt->status & BIT(channel % 8));
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_temp_read(struct device *dev, u32 attr, int channel,
+			     long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char temp_en, temp_hyst;
+	signed char temp_max;
+	int ret, temp_raw;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_temp_enable:
+		temp_en = data->hwmon_en.tin_en[channel / 8];
+		*val = !!(temp_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_temp_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_TIN_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->tin))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->tin);
+		if (ret)
+			return ret;
+
+		temp_raw = data->rpt->tin.msb << 3;
+		temp_raw |= FIELD_GET(NCT6694_LSB_REG_MASK, data->rpt->tin.lsb);
+
+		/* Real temperature(milli degrees Celsius) = temp_raw * 1000 * 0.125 */
+		*val = sign_extend32(temp_raw, 10) * 125;
+
+		return 0;
+	case hwmon_temp_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = temp_from_reg(data->msg->hwmon_alarm.tin_cfg[channel].hl);
+
+		return 0;
+	case hwmon_temp_max_hyst:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+		temp_hyst = FIELD_GET(NCT6694_TIN_HYST_MASK,
+				      data->msg->hwmon_alarm.tin_cfg[channel].hyst);
+		*val = temp_from_reg(temp_max - temp_hyst);
+
+		return 0;
+	case hwmon_temp_max_alarm:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_TIN_STS(channel / 8)),
+			.len = cpu_to_le16(sizeof(data->rpt->status))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->status);
+		if (ret)
+			return ret;
+
+		*val = !!(data->rpt->status & BIT(channel % 8));
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_fan_read(struct device *dev, u32 attr, int channel,
+			    long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char fanin_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_fan_enable:
+		fanin_en = data->hwmon_en.fin_en[channel / 8];
+		*val = !!(fanin_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_fan_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_FIN_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->fin))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->fin);
+		if (ret)
+			return ret;
+
+		*val = be16_to_cpu(data->rpt->fin);
+
+		return 0;
+	case hwmon_fan_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		*val = be16_to_cpu(data->msg->hwmon_alarm.fin_ll[channel]);
+
+		return 0;
+	case hwmon_fan_min_alarm:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_FIN_STS(channel / 8)),
+			.len = cpu_to_le16(sizeof(data->rpt->status))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->status);
+		if (ret)
+			return ret;
+
+		*val = !!(data->rpt->status & BIT(channel % 8));
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_pwm_read(struct device *dev, u32 attr, int channel,
+			    long *val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char pwm_en;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		pwm_en = data->hwmon_en.pwm_en[channel / 8];
+		*val = !!(pwm_en & BIT(channel % 8));
+
+		return 0;
+	case hwmon_pwm_input:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_RPT_MOD,
+			.offset = cpu_to_le16(NCT6694_PWM_IDX(channel)),
+			.len = cpu_to_le16(sizeof(data->rpt->pwm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->rpt->pwm);
+		if (ret)
+			return ret;
+
+		*val = data->rpt->pwm;
+
+		return 0;
+	case hwmon_pwm_freq:
+		*val = NCT6694_FREQ_FROM_REG(data->hwmon_en.pwm_freq[channel]);
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_in_write(struct device *dev, u32 attr, int channel,
+			    long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_in_enable:
+		if (val == 0)
+			data->hwmon_en.vin_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.vin_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_in_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 0, 2032);
+		data->msg->hwmon_alarm.vin_limit[channel].hl = in_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	case hwmon_in_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 0, 2032);
+		data->msg->hwmon_alarm.vin_limit[channel].ll = in_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_temp_write(struct device *dev, u32 attr, int channel,
+			      long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	unsigned char temp_hyst;
+	signed char temp_max;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_temp_enable:
+		if (val == 0)
+			data->hwmon_en.tin_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.tin_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_temp_max:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, -127000, 127000);
+		data->msg->hwmon_alarm.tin_cfg[channel].hl = temp_to_reg(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	case hwmon_temp_max_hyst:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+
+		val = clamp_val(val, -127000, 127000);
+		temp_max = data->msg->hwmon_alarm.tin_cfg[channel].hl;
+		temp_hyst = temp_max - temp_to_reg(val);
+		temp_hyst = clamp_val(temp_hyst, 0, 7);
+		data->msg->hwmon_alarm.tin_cfg[channel].hyst =
+			(data->msg->hwmon_alarm.tin_cfg[channel].hyst & ~NCT6694_TIN_HYST_MASK) |
+			FIELD_PREP(NCT6694_TIN_HYST_MASK, temp_hyst);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_fan_write(struct device *dev, u32 attr, int channel,
+			     long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_fan_enable:
+		if (val == 0)
+			data->hwmon_en.fin_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.fin_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_fan_min:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_ALARM,
+			.sel = NCT6694_HWMON_ALARM_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+		};
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->hwmon_alarm);
+		if (ret)
+			return ret;
+
+		val = clamp_val(val, 1, 65535);
+		data->msg->hwmon_alarm.fin_ll[channel] = cpu_to_be16(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->hwmon_alarm);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_pwm_write(struct device *dev, u32 attr, int channel,
+			     long val)
+{
+	struct nct6694_hwmon_data *data = dev_get_drvdata(dev);
+	struct nct6694_cmd_header cmd_hd;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	switch (attr) {
+	case hwmon_pwm_enable:
+		if (val == 0)
+			data->hwmon_en.pwm_en[channel / 8] &= ~BIT(channel % 8);
+		else if (val == 1)
+			data->hwmon_en.pwm_en[channel / 8] |= BIT(channel % 8);
+		else
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	case hwmon_pwm_input:
+		if (val < 0 || val > 255)
+			return -EINVAL;
+
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_PWM_MOD,
+			.cmd = NCT6694_PWM_CONTROL,
+			.sel = NCT6694_PWM_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->msg->pwm_ctrl))
+		};
+
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+				       &data->msg->pwm_ctrl);
+		if (ret)
+			return ret;
+
+		data->msg->pwm_ctrl.mal_val[channel] = val;
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->msg->pwm_ctrl);
+	case hwmon_pwm_freq:
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_HWMON_MOD,
+			.cmd = NCT6694_HWMON_CONTROL,
+			.sel = NCT6694_HWMON_CONTROL_SEL,
+			.len = cpu_to_le16(sizeof(data->hwmon_en))
+		};
+
+		data->hwmon_en.pwm_freq[channel] = NCT6694_FREQ_TO_REG(val);
+
+		return nct6694_write_msg(data->nct6694, &cmd_hd,
+					 &data->hwmon_en);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_read(struct device *dev, enum hwmon_sensor_types type,
+			u32 attr, int channel, long *val)
+{
+	switch (type) {
+	case hwmon_in:
+		/* in mV */
+		return nct6694_in_read(dev, attr, channel, val);
+	case hwmon_temp:
+		/* in mC */
+		return nct6694_temp_read(dev, attr, channel, val);
+	case hwmon_fan:
+		/* in RPM */
+		return nct6694_fan_read(dev, attr, channel, val);
+	case hwmon_pwm:
+		/* in value 0~255 */
+		return nct6694_pwm_read(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int nct6694_write(struct device *dev, enum hwmon_sensor_types type,
+			 u32 attr, int channel, long val)
+{
+	switch (type) {
+	case hwmon_in:
+		return nct6694_in_write(dev, attr, channel, val);
+	case hwmon_temp:
+		return nct6694_temp_write(dev, attr, channel, val);
+	case hwmon_fan:
+		return nct6694_fan_write(dev, attr, channel, val);
+	case hwmon_pwm:
+		return nct6694_pwm_write(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static umode_t nct6694_is_visible(const void *data,
+				  enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	switch (type) {
+	case hwmon_in:
+		switch (attr) {
+		case hwmon_in_enable:
+		case hwmon_in_max:
+		case hwmon_in_min:
+			return 0644;
+		case hwmon_in_alarm:
+		case hwmon_in_input:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_enable:
+		case hwmon_temp_max:
+		case hwmon_temp_max_hyst:
+			return 0644;
+		case hwmon_temp_input:
+		case hwmon_temp_max_alarm:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_fan:
+		switch (attr) {
+		case hwmon_fan_enable:
+		case hwmon_fan_min:
+			return 0644;
+		case hwmon_fan_input:
+		case hwmon_fan_min_alarm:
+			return 0444;
+		default:
+			return 0;
+		}
+	case hwmon_pwm:
+		switch (attr) {
+		case hwmon_pwm_enable:
+		case hwmon_pwm_freq:
+		case hwmon_pwm_input:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+static const struct hwmon_ops nct6694_hwmon_ops = {
+	.is_visible = nct6694_is_visible,
+	.read = nct6694_read,
+	.write = nct6694_write,
+};
+
+static const struct hwmon_chip_info nct6694_chip_info = {
+	.ops = &nct6694_hwmon_ops,
+	.info = nct6694_info,
+};
+
+static int nct6694_hwmon_init(struct nct6694_hwmon_data *data)
+{
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_HWMON_MOD,
+		.cmd = NCT6694_HWMON_CONTROL,
+		.sel = NCT6694_HWMON_CONTROL_SEL,
+		.len = cpu_to_le16(sizeof(data->hwmon_en))
+	};
+	int ret;
+
+	/*
+	 * Record each Hardware Monitor Channel enable status
+	 * and PWM frequency register
+	 */
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+			       &data->hwmon_en);
+	if (ret)
+		return ret;
+
+	cmd_hd = (struct nct6694_cmd_header) {
+		.mod = NCT6694_HWMON_MOD,
+		.cmd = NCT6694_HWMON_ALARM,
+		.sel = NCT6694_HWMON_ALARM_SEL,
+		.len = cpu_to_le16(sizeof(data->msg->hwmon_alarm))
+	};
+
+	/* Select hwmon device alarm mode */
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd,
+			       &data->msg->hwmon_alarm);
+	if (ret)
+		return ret;
+
+	data->msg->hwmon_alarm.smi_ctrl = NCT6694_HWMON_REALTIME_IRQ;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd,
+				 &data->msg->hwmon_alarm);
+}
+
+static int nct6694_hwmon_probe(struct platform_device *pdev)
+{
+	struct nct6694_hwmon_data *data;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct device *hwmon_dev;
+	int ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->rpt = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_rpt),
+				 GFP_KERNEL);
+	if (!data->rpt)
+		return -ENOMEM;
+
+	data->msg = devm_kzalloc(&pdev->dev, sizeof(union nct6694_hwmon_msg),
+				 GFP_KERNEL);
+	if (!data->msg)
+		return -ENOMEM;
+
+	data->nct6694 = nct6694;
+	devm_mutex_init(&pdev->dev, &data->lock);
+
+	ret = nct6694_hwmon_init(data);
+	if (ret)
+		return ret;
+
+	/* Register hwmon device to HWMON framework */
+	hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+							 "nct6694", data,
+							 &nct6694_chip_info,
+							 NULL);
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static struct platform_driver nct6694_hwmon_driver = {
+	.driver = {
+		.name	= "nct6694-hwmon",
+	},
+	.probe		= nct6694_hwmon_probe,
+};
+
+module_platform_driver(nct6694_hwmon_driver);
+
+MODULE_DESCRIPTION("USB-HWMON driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-hwmon");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 5/7] watchdog: Add Nuvoton NCT6694 WDT support
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

This driver supports Watchdog timer functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS                    |   1 +
 drivers/watchdog/Kconfig       |  11 ++
 drivers/watchdog/Makefile      |   1 +
 drivers/watchdog/nct6694_wdt.c | 295 +++++++++++++++++++++++++++++++++
 4 files changed, 308 insertions(+)
 create mode 100644 drivers/watchdog/nct6694_wdt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6e9b78202d6f..a772747d493f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16725,6 +16725,7 @@ F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	drivers/net/can/usb/nct6694_canfd.c
+F:	drivers/watchdog/nct6694_wdt.c
 F:	include/linux/mfd/nct6694.h
 
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index f81705f8539a..4c4f826368c4 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -748,6 +748,17 @@ config MAX77620_WATCHDOG
 	  MAX77620 chips. To compile this driver as a module,
 	  choose M here: the module will be called max77620_wdt.
 
+config NCT6694_WATCHDOG
+	tristate "Nuvoton NCT6694 watchdog support"
+	depends on MFD_NCT6694
+	select WATCHDOG_CORE
+	help
+	  Say Y here to support Nuvoton NCT6694 watchdog timer
+	  functionality.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nct6694_wdt.
+
 config IMX2_WDT
 	tristate "IMX2+ Watchdog"
 	depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 8411626fa162..de2a04ff8a92 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -232,6 +232,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
 obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
 obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
 obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o
+obj-$(CONFIG_NCT6694_WATCHDOG) += nct6694_wdt.o
 obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
 obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
 obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
diff --git a/drivers/watchdog/nct6694_wdt.c b/drivers/watchdog/nct6694_wdt.c
new file mode 100644
index 000000000000..351867c2fd1d
--- /dev/null
+++ b/drivers/watchdog/nct6694_wdt.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 WDT driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+#define DRVNAME "nct6694-wdt"
+
+#define NCT6694_DEFAULT_TIMEOUT		10
+#define NCT6694_DEFAULT_PRETIMEOUT	0
+
+/*
+ * USB command module type for NCT6694 WDT controller.
+ * This defines the module type used for communication with the NCT6694
+ * WDT controller over the USB interface.
+ */
+#define NCT6694_WDT_MOD			0x07
+
+/* Command 00h - WDT Setup */
+#define NCT6694_WDT_SETUP		0x00
+#define NCT6694_WDT_SETUP_SEL(idx)	(idx ? 0x01 : 0x00)
+
+/* Command 01h - WDT Command */
+#define NCT6694_WDT_COMMAND		0x01
+#define NCT6694_WDT_COMMAND_SEL(idx)	(idx ? 0x01 : 0x00)
+
+static unsigned int timeout = NCT6694_DEFAULT_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds");
+
+static unsigned int pretimeout = NCT6694_DEFAULT_PRETIMEOUT;
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout, "Watchdog pre-timeout in seconds");
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+			   __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+enum {
+	NCT6694_ACTION_NONE = 0,
+	NCT6694_ACTION_SIRQ,
+	NCT6694_ACTION_GPO,
+};
+
+struct __packed nct6694_wdt_setup {
+	__le32 pretimeout;
+	__le32 timeout;
+	u8 owner;
+	u8 scratch;
+	u8 control;
+	u8 status;
+	__le32 countdown;
+};
+
+struct __packed nct6694_wdt_cmd {
+	__le32 wdt_cmd;
+	__le32 reserved;
+};
+
+union __packed nct6694_wdt_msg {
+	struct nct6694_wdt_setup setup;
+	struct nct6694_wdt_cmd cmd;
+};
+
+struct nct6694_wdt_data {
+	struct watchdog_device wdev;
+	struct device *dev;
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	union nct6694_wdt_msg *msg;
+	unsigned int wdev_idx;
+};
+
+static int nct6694_wdt_setting(struct watchdog_device *wdev,
+			       u32 timeout_val, u8 timeout_act,
+			       u32 pretimeout_val, u8 pretimeout_act)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_setup *setup = &data->msg->setup;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_SETUP,
+		.sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*setup))
+	};
+	unsigned int timeout_fmt, pretimeout_fmt;
+
+	guard(mutex)(&data->lock);
+
+	if (pretimeout_val == 0)
+		pretimeout_act = NCT6694_ACTION_NONE;
+
+	timeout_fmt = (timeout_val * 1000) | (timeout_act << 24);
+	pretimeout_fmt = (pretimeout_val * 1000) | (pretimeout_act << 24);
+
+	memset(setup, 0, sizeof(*setup));
+	setup->timeout = cpu_to_le32(timeout_fmt);
+	setup->pretimeout = cpu_to_le32(pretimeout_fmt);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, setup);
+}
+
+static int nct6694_wdt_start(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+				  wdev->pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	dev_dbg(data->dev, "Setting WDT(%d): timeout = %d, pretimeout = %d\n",
+		data->wdev_idx, wdev->timeout, wdev->pretimeout);
+
+	return ret;
+}
+
+static int nct6694_wdt_stop(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_COMMAND,
+		.sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*cmd))
+	};
+
+	guard(mutex)(&data->lock);
+
+	memcpy(&cmd->wdt_cmd, "WDTC", 4);
+	cmd->reserved = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
+}
+
+static int nct6694_wdt_ping(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_cmd *cmd = &data->msg->cmd;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_COMMAND,
+		.sel = NCT6694_WDT_COMMAND_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*cmd))
+	};
+
+	guard(mutex)(&data->lock);
+	memcpy(&cmd->wdt_cmd, "WDTS", 4);
+	cmd->reserved = 0;
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, cmd);
+}
+
+static int nct6694_wdt_set_timeout(struct watchdog_device *wdev,
+				   unsigned int timeout)
+{
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, timeout, NCT6694_ACTION_GPO,
+				  wdev->pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	wdev->timeout = timeout;
+
+	return 0;
+}
+
+static int nct6694_wdt_set_pretimeout(struct watchdog_device *wdev,
+				      unsigned int pretimeout)
+{
+	int ret;
+
+	ret = nct6694_wdt_setting(wdev, wdev->timeout, NCT6694_ACTION_GPO,
+				  pretimeout, NCT6694_ACTION_GPO);
+	if (ret)
+		return ret;
+
+	wdev->pretimeout = pretimeout;
+
+	return 0;
+}
+
+static unsigned int nct6694_wdt_get_time(struct watchdog_device *wdev)
+{
+	struct nct6694_wdt_data *data = watchdog_get_drvdata(wdev);
+	struct nct6694_wdt_setup *setup = &data->msg->setup;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_WDT_MOD,
+		.cmd = NCT6694_WDT_SETUP,
+		.sel = NCT6694_WDT_SETUP_SEL(data->wdev_idx),
+		.len = cpu_to_le16(sizeof(*setup))
+	};
+	unsigned int timeleft_ms;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, setup);
+	if (ret)
+		return 0;
+
+	timeleft_ms = le32_to_cpu(setup->countdown);
+
+	return timeleft_ms / 1000;
+}
+
+static const struct watchdog_info nct6694_wdt_info = {
+	.options = WDIOF_SETTIMEOUT	|
+		   WDIOF_KEEPALIVEPING	|
+		   WDIOF_MAGICCLOSE	|
+		   WDIOF_PRETIMEOUT,
+	.identity = DRVNAME,
+};
+
+static const struct watchdog_ops nct6694_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = nct6694_wdt_start,
+	.stop = nct6694_wdt_stop,
+	.set_timeout = nct6694_wdt_set_timeout,
+	.set_pretimeout = nct6694_wdt_set_pretimeout,
+	.get_timeleft = nct6694_wdt_get_time,
+	.ping = nct6694_wdt_ping,
+};
+
+static int nct6694_wdt_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct device *dev = &pdev->dev;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_wdt_data *data;
+	struct watchdog_device *wdev;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->msg = devm_kzalloc(dev, sizeof(union nct6694_wdt_msg),
+				 GFP_KERNEL);
+	if (!data->msg)
+		return -ENOMEM;
+
+	data->dev = dev;
+	data->nct6694 = nct6694;
+	data->wdev_idx = cell->id;
+
+	wdev = &data->wdev;
+	wdev->info = &nct6694_wdt_info;
+	wdev->ops = &nct6694_wdt_ops;
+	wdev->timeout = timeout;
+	wdev->pretimeout = pretimeout;
+	if (timeout < pretimeout) {
+		dev_warn(data->dev, "pretimeout < timeout. Setting to zero\n");
+		wdev->pretimeout = 0;
+	}
+
+	wdev->min_timeout = 1;
+	wdev->max_timeout = 255;
+
+	devm_mutex_init(dev, &data->lock);
+
+	platform_set_drvdata(pdev, data);
+
+	watchdog_set_drvdata(&data->wdev, data);
+	watchdog_set_nowayout(&data->wdev, nowayout);
+	watchdog_stop_on_reboot(&data->wdev);
+
+	return devm_watchdog_register_device(dev, &data->wdev);
+}
+
+static struct platform_driver nct6694_wdt_driver = {
+	.driver = {
+		.name	= DRVNAME,
+	},
+	.probe		= nct6694_wdt_probe,
+};
+
+module_platform_driver(nct6694_wdt_driver);
+
+MODULE_DESCRIPTION("USB-WDT driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-wdt");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 4/7] can: Add Nuvoton NCT6694 CAN support
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

This driver supports Socket CANfd functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS                         |   1 +
 drivers/net/can/usb/Kconfig         |  10 +
 drivers/net/can/usb/Makefile        |   1 +
 drivers/net/can/usb/nct6694_canfd.c | 856 ++++++++++++++++++++++++++++
 4 files changed, 868 insertions(+)
 create mode 100644 drivers/net/can/usb/nct6694_canfd.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 4e72f749cdf2..6e9b78202d6f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16724,6 +16724,7 @@ S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
+F:	drivers/net/can/usb/nct6694_canfd.c
 F:	include/linux/mfd/nct6694.h
 
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
diff --git a/drivers/net/can/usb/Kconfig b/drivers/net/can/usb/Kconfig
index 9dae0c71a2e1..53254012cdc4 100644
--- a/drivers/net/can/usb/Kconfig
+++ b/drivers/net/can/usb/Kconfig
@@ -133,6 +133,16 @@ config CAN_MCBA_USB
 	  This driver supports the CAN BUS Analyzer interface
 	  from Microchip (http://www.microchip.com/development-tools/).
 
+config CAN_NCT6694
+	tristate "Nuvoton NCT6694 Socket CANfd support"
+	depends on MFD_NCT6694
+	help
+	  If you say yes to this option, support will be included for Nuvoton
+	  NCT6694, a USB device to socket CANfd controller.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called nct6694_canfd.
+
 config CAN_PEAK_USB
 	tristate "PEAK PCAN-USB/USB Pro interfaces for CAN 2.0b/CAN-FD"
 	help
diff --git a/drivers/net/can/usb/Makefile b/drivers/net/can/usb/Makefile
index 8b11088e9a59..fcafb1ac262e 100644
--- a/drivers/net/can/usb/Makefile
+++ b/drivers/net/can/usb/Makefile
@@ -11,5 +11,6 @@ obj-$(CONFIG_CAN_F81604) += f81604.o
 obj-$(CONFIG_CAN_GS_USB) += gs_usb.o
 obj-$(CONFIG_CAN_KVASER_USB) += kvaser_usb/
 obj-$(CONFIG_CAN_MCBA_USB) += mcba_usb.o
+obj-$(CONFIG_CAN_NCT6694) += nct6694_canfd.o
 obj-$(CONFIG_CAN_PEAK_USB) += peak_usb/
 obj-$(CONFIG_CAN_UCAN) += ucan.o
diff --git a/drivers/net/can/usb/nct6694_canfd.c b/drivers/net/can/usb/nct6694_canfd.c
new file mode 100644
index 000000000000..7a15c39021ff
--- /dev/null
+++ b/drivers/net/can/usb/nct6694_canfd.c
@@ -0,0 +1,856 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 Socket CANfd driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/can/dev.h>
+#include <linux/can/rx-offload.h>
+#include <linux/ethtool.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/platform_device.h>
+
+#define DRVNAME "nct6694-can"
+
+/*
+ * USB command module type for NCT6694 CANfd controller.
+ * This defines the module type used for communication with the NCT6694
+ * CANfd controller over the USB interface.
+ */
+#define NCT6694_CAN_MOD			0x05
+
+/* Command 00h - CAN Setting and Initialization */
+#define NCT6694_CAN_SETTING		0x00
+#define NCT6694_CAN_SETTING_SEL(idx)	(idx ? 0x01 : 0x00)
+#define NCT6694_CAN_SETTING_CTRL1_MON	BIT(0)
+#define NCT6694_CAN_SETTING_CTRL1_NISO	BIT(1)
+#define NCT6694_CAN_SETTING_CTRL1_LBCK	BIT(2)
+
+/* Command 01h - CAN Information */
+#define NCT6694_CAN_INFORMATION		0x01
+#define NCT6694_CAN_INFORMATION_SEL	0x00
+
+/* Command 02h - CAN Event */
+#define NCT6694_CAN_EVENT		0x02
+#define NCT6694_CAN_EVENT_SEL(idx, mask)	\
+	((idx ? 0x80 : 0x00) | ((mask) & 0xFF))
+#define NCT6694_CAN_EVENT_ERR		BIT(0)
+#define NCT6694_CAN_EVENT_STATUS	BIT(1)
+#define NCT6694_CAN_EVENT_TX_EVT	BIT(2)
+#define NCT6694_CAN_EVENT_RX_EVT	BIT(3)
+#define NCT6694_CAN_EVENT_REC		BIT(4)
+#define NCT6694_CAN_EVENT_TEC		BIT(5)
+#define NCT6694_CAN_EVENT_MASK		GENMASK(3, 0)
+#define NCT6694_CAN_EVT_TX_FIFO_EMPTY	BIT(7)	/* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_LOST	BIT(5)	/* Read-clear */
+#define NCT6694_CAN_EVT_RX_HALF_FULL	BIT(6)	/* Read-clear */
+#define NCT6694_CAN_EVT_RX_DATA_IN	BIT(7)	/* Read-clear*/
+
+/* Command 10h - CAN Deliver */
+#define NCT6694_CAN_DELIVER		0x10
+#define NCT6694_CAN_DELIVER_SEL(buf_cnt)	\
+	((buf_cnt) & 0xFF)
+
+/* Command 11h - CAN Receive */
+#define NCT6694_CAN_RECEIVE		0x11
+#define NCT6694_CAN_RECEIVE_SEL(idx, buf_cnt)	\
+	((idx ? 0x80 : 0x00) | ((buf_cnt) & 0xFF))
+
+#define NCT6694_CAN_FRAME_TAG_CAN0	0xC0
+#define NCT6694_CAN_FRAME_TAG_CAN1	0xC1
+#define NCT6694_CAN_FRAME_FLAG_EFF	BIT(0)
+#define NCT6694_CAN_FRAME_FLAG_RTR	BIT(1)
+#define NCT6694_CAN_FRAME_FLAG_FD	BIT(2)
+#define NCT6694_CAN_FRAME_FLAG_BRS	BIT(3)
+#define NCT6694_CAN_FRAME_FLAG_ERR	BIT(4)
+
+#define NCT6694_NAPI_WEIGHT		32
+
+enum nct6694_event_err {
+	NCT6694_CAN_EVT_ERR_NO_ERROR = 0,
+	NCT6694_CAN_EVT_ERR_CRC_ERROR,
+	NCT6694_CAN_EVT_ERR_STUFF_ERROR,
+	NCT6694_CAN_EVT_ERR_ACK_ERROR,
+	NCT6694_CAN_EVT_ERR_FORM_ERROR,
+	NCT6694_CAN_EVT_ERR_BIT_ERROR,
+	NCT6694_CAN_EVT_ERR_TIMEOUT_ERROR,
+	NCT6694_CAN_EVT_ERR_UNKNOWN_ERROR,
+};
+
+enum nct6694_event_status {
+	NCT6694_CAN_EVT_STS_ERROR_ACTIVE = 0,
+	NCT6694_CAN_EVT_STS_ERROR_PASSIVE,
+	NCT6694_CAN_EVT_STS_BUS_OFF,
+	NCT6694_CAN_EVT_STS_WARNING,
+};
+
+struct __packed nct6694_can_setting {
+	__le32 nbr;
+	__le32 dbr;
+	u8 active;
+	u8 reserved[3];
+	__le16 ctrl1;
+	__le16 ctrl2;
+	__le32 nbtp;
+	__le32 dbtp;
+};
+
+struct __packed nct6694_can_information {
+	u8 tx_fifo_cnt;
+	u8 rx_fifo_cnt;
+	u8 reserved[2];
+	__le32 can_clk;
+};
+
+struct __packed nct6694_can_event {
+	u8 err;
+	u8 status;
+	u8 tx_evt;
+	u8 rx_evt;
+	u8 rec;
+	u8 tec;
+	u8 reserved[2];
+};
+
+struct __packed nct6694_can_frame {
+	u8 tag;
+	u8 flag;
+	u8 reserved;
+	u8 length;
+	__le32 id;
+	u8 data[64];
+};
+
+union __packed nct6694_can_tx {
+	struct nct6694_can_frame frame;
+	struct nct6694_can_setting setting;
+};
+
+union __packed nct6694_can_rx {
+	struct nct6694_can_event event[2];
+	struct nct6694_can_frame frame;
+	struct nct6694_can_information info;
+};
+
+struct nct6694_can_priv {
+	struct can_priv can;	/* must be the first member */
+	struct can_rx_offload offload;
+	struct net_device *ndev;
+	struct nct6694 *nct6694;
+	struct mutex lock;
+	struct sk_buff *tx_skb;
+	struct workqueue_struct *wq;
+	struct work_struct tx_work;
+	union nct6694_can_tx *tx;
+	union nct6694_can_rx *rx;
+	unsigned char can_idx;
+};
+
+static inline struct nct6694_can_priv *rx_offload_to_priv(struct can_rx_offload *offload)
+{
+	return container_of(offload, struct nct6694_can_priv, offload);
+}
+
+static const struct can_bittiming_const nct6694_can_bittiming_nominal_const = {
+	.name = DRVNAME,
+	.tseg1_min = 2,
+	.tseg1_max = 256,
+	.tseg2_min = 2,
+	.tseg2_max = 128,
+	.sjw_max = 128,
+	.brp_min = 1,
+	.brp_max = 511,
+	.brp_inc = 1,
+};
+
+static const struct can_bittiming_const nct6694_can_bittiming_data_const = {
+	.name = DRVNAME,
+	.tseg1_min = 1,
+	.tseg1_max = 32,
+	.tseg2_min = 1,
+	.tseg2_max = 16,
+	.sjw_max = 16,
+	.brp_min = 1,
+	.brp_max = 31,
+	.brp_inc = 1,
+};
+
+static void nct6694_can_rx_offload(struct can_rx_offload *offload,
+				   struct sk_buff *skb)
+{
+	struct nct6694_can_priv *priv = rx_offload_to_priv(offload);
+	int ret;
+
+	ret = can_rx_offload_queue_tail(offload, skb);
+	if (ret)
+		priv->ndev->stats.rx_fifo_errors++;
+}
+
+static void nct6694_can_handle_lost_msg(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	netdev_err(ndev, "RX FIFO overflow, message(s) lost.\n");
+
+	stats->rx_errors++;
+	stats->rx_over_errors++;
+
+	skb = alloc_can_err_skb(ndev, &cf);
+	if (!skb)
+		return;
+
+	cf->can_id |= CAN_ERR_CRTL;
+	cf->data[1] = CAN_ERR_CRTL_RX_OVERFLOW;
+
+	nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_can_rx(struct net_device *ndev, u8 rx_evt)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_frame *frame = &priv->rx->frame;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CAN_MOD,
+		.cmd = NCT6694_CAN_RECEIVE,
+		.sel = NCT6694_CAN_RECEIVE_SEL(priv->can_idx, 1),
+		.len = cpu_to_le16(sizeof(*frame))
+	};
+	struct canfd_frame *cfd;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+	int ret;
+
+	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, frame);
+	if (ret)
+		return;
+
+	if (frame->flag & NCT6694_CAN_FRAME_FLAG_FD) {
+		skb = alloc_canfd_skb(priv->ndev, &cfd);
+		if (!skb)
+			return;
+
+		cfd->can_id = le32_to_cpu(frame->id);
+		cfd->len = frame->length;
+		if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
+			cfd->can_id |= CAN_EFF_FLAG;
+		if (frame->flag & NCT6694_CAN_FRAME_FLAG_BRS)
+			cfd->flags |= CANFD_BRS;
+		if (frame->flag & NCT6694_CAN_FRAME_FLAG_ERR)
+			cfd->flags |= CANFD_ESI;
+
+		memcpy(cfd->data, frame->data, cfd->len);
+	} else {
+		skb = alloc_can_skb(priv->ndev, &cf);
+		if (!skb)
+			return;
+
+		cf->can_id = le32_to_cpu(frame->id);
+		cf->len = frame->length;
+		if (frame->flag & NCT6694_CAN_FRAME_FLAG_EFF)
+			cf->can_id |= CAN_EFF_FLAG;
+		if (frame->flag & NCT6694_CAN_FRAME_FLAG_RTR)
+			cf->can_id |= CAN_RTR_FLAG;
+
+		memcpy(cf->data, frame->data, cf->len);
+	}
+
+	nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_can_clean(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+	if (priv->tx_skb || netif_queue_stopped(ndev))
+		ndev->stats.tx_errors++;
+	dev_kfree_skb(priv->tx_skb);
+	priv->tx_skb = NULL;
+}
+
+static int nct6694_can_get_berr_counter(const struct net_device *ndev,
+					struct can_berr_counter *bec)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_event *evt = priv->rx->event;
+	struct nct6694_cmd_header cmd_hd;
+	u8 mask = NCT6694_CAN_EVENT_REC | NCT6694_CAN_EVENT_TEC;
+	int ret;
+
+	guard(mutex)(&priv->lock);
+
+	cmd_hd = (struct nct6694_cmd_header) {
+		.mod = NCT6694_CAN_MOD,
+		.cmd = NCT6694_CAN_EVENT,
+		.sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask),
+		.len = cpu_to_le16(sizeof(priv->rx->event))
+	};
+
+	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, evt);
+	if (ret < 0)
+		return ret;
+
+	bec->rxerr = evt[priv->can_idx].rec;
+	bec->txerr = evt[priv->can_idx].tec;
+
+	return 0;
+}
+
+static void nct6694_can_handle_state_change(struct net_device *ndev,
+					    u8 status)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	enum can_state new_state = priv->can.state;
+	enum can_state rx_state, tx_state;
+	struct can_berr_counter bec;
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	nct6694_can_get_berr_counter(ndev, &bec);
+	can_state_get_by_berr_counter(ndev, &bec, &tx_state, &rx_state);
+
+	switch (status) {
+	case NCT6694_CAN_EVT_STS_ERROR_ACTIVE:
+		new_state = CAN_STATE_ERROR_ACTIVE;
+		break;
+	case NCT6694_CAN_EVT_STS_ERROR_PASSIVE:
+		new_state = CAN_STATE_ERROR_PASSIVE;
+		break;
+	case NCT6694_CAN_EVT_STS_BUS_OFF:
+		new_state = CAN_STATE_BUS_OFF;
+		break;
+	case NCT6694_CAN_EVT_STS_WARNING:
+		new_state = CAN_STATE_ERROR_WARNING;
+		break;
+	default:
+		netdev_err(ndev, "Receive unknown CAN status event.\n");
+		return;
+	}
+
+	/* state hasn't changed */
+	if (new_state == priv->can.state)
+		return;
+
+	skb = alloc_can_err_skb(ndev, &cf);
+
+	tx_state = bec.txerr >= bec.rxerr ? new_state : 0;
+	rx_state = bec.txerr <= bec.rxerr ? new_state : 0;
+	can_change_state(ndev, cf, tx_state, rx_state);
+
+	if (new_state == CAN_STATE_BUS_OFF) {
+		can_bus_off(ndev);
+	} else if (skb) {
+		cf->can_id |= CAN_ERR_CNT;
+		cf->data[6] = bec.txerr;
+		cf->data[7] = bec.rxerr;
+	}
+
+	nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_handle_bus_err(struct net_device *ndev, u8 bus_err)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct can_frame *cf;
+	struct sk_buff *skb;
+
+	if (bus_err == NCT6694_CAN_EVT_ERR_NO_ERROR)
+		return;
+
+	priv->can.can_stats.bus_error++;
+
+	skb = alloc_can_err_skb(ndev, &cf);
+	if (skb)
+		cf->can_id |= CAN_ERR_PROT | CAN_ERR_BUSERROR;
+
+	switch (bus_err) {
+	case NCT6694_CAN_EVT_ERR_CRC_ERROR:
+		netdev_dbg(ndev, "CRC error\n");
+		ndev->stats.rx_errors++;
+		if (skb)
+			cf->data[3] |= CAN_ERR_PROT_LOC_CRC_SEQ;
+		break;
+
+	case NCT6694_CAN_EVT_ERR_STUFF_ERROR:
+		netdev_dbg(ndev, "Stuff error\n");
+		ndev->stats.rx_errors++;
+		if (skb)
+			cf->data[2] |= CAN_ERR_PROT_STUFF;
+		break;
+
+	case NCT6694_CAN_EVT_ERR_ACK_ERROR:
+		netdev_dbg(ndev, "Ack error\n");
+		ndev->stats.tx_errors++;
+		if (skb) {
+			cf->can_id |= CAN_ERR_ACK;
+			cf->data[2] |= CAN_ERR_PROT_TX;
+		}
+		break;
+
+	case NCT6694_CAN_EVT_ERR_FORM_ERROR:
+		netdev_dbg(ndev, "Form error\n");
+		ndev->stats.rx_errors++;
+		if (skb)
+			cf->data[2] |= CAN_ERR_PROT_FORM;
+		break;
+
+	case NCT6694_CAN_EVT_ERR_BIT_ERROR:
+		netdev_dbg(ndev, "Bit error\n");
+		ndev->stats.tx_errors++;
+		if (skb)
+			cf->data[2] |= CAN_ERR_PROT_TX | CAN_ERR_PROT_BIT;
+		break;
+
+	default:
+		break;
+	}
+
+	nct6694_can_rx_offload(&priv->offload, skb);
+}
+
+static void nct6694_can_tx_irq(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct net_device_stats *stats = &ndev->stats;
+
+	guard(mutex)(&priv->lock);
+	stats->tx_bytes += can_get_echo_skb(ndev, 0, NULL);
+	stats->tx_packets++;
+	netif_wake_queue(ndev);
+}
+
+static irqreturn_t nct6694_can_irq(int irq, void *data)
+{
+	struct net_device *ndev = data;
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_event *evt = priv->rx->event;
+	struct nct6694_cmd_header cmd_hd;
+	u8 tx_evt, rx_evt, bus_err, can_status;
+	u8 mask_sts = NCT6694_CAN_EVENT_MASK;
+	irqreturn_t handled = IRQ_NONE;
+	int can_idx = priv->can_idx;
+	int ret;
+
+	scoped_guard(mutex, &priv->lock) {
+		cmd_hd = (struct nct6694_cmd_header) {
+			.mod = NCT6694_CAN_MOD,
+			.cmd = NCT6694_CAN_EVENT,
+			.sel = NCT6694_CAN_EVENT_SEL(priv->can_idx, mask_sts),
+			.len = cpu_to_le16(sizeof(priv->rx->event))
+		};
+
+		ret = nct6694_read_msg(priv->nct6694, &cmd_hd, evt);
+		if (ret < 0)
+			return handled;
+
+		tx_evt = evt[can_idx].tx_evt;
+		rx_evt = evt[can_idx].rx_evt;
+		bus_err = evt[can_idx].err;
+		can_status = evt[can_idx].status;
+	}
+
+	if (rx_evt & NCT6694_CAN_EVT_RX_DATA_IN) {
+		nct6694_can_rx(ndev, rx_evt);
+		handled = IRQ_HANDLED;
+	}
+
+	if (rx_evt & NCT6694_CAN_EVT_RX_DATA_LOST) {
+		nct6694_can_handle_lost_msg(ndev);
+		handled = IRQ_HANDLED;
+	}
+
+	if (can_status) {
+		nct6694_can_handle_state_change(ndev, can_status);
+		handled = IRQ_HANDLED;
+	}
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_BERR_REPORTING) {
+		nct6694_handle_bus_err(ndev, bus_err);
+		handled = IRQ_HANDLED;
+	}
+
+	if (handled)
+		can_rx_offload_threaded_irq_finish(&priv->offload);
+
+	if (tx_evt & NCT6694_CAN_EVT_TX_FIFO_EMPTY)
+		nct6694_can_tx_irq(ndev);
+
+	return handled;
+}
+
+static void nct6694_can_tx(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_frame *frame = &priv->tx->frame;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CAN_MOD,
+		.cmd = NCT6694_CAN_DELIVER,
+		.sel = NCT6694_CAN_DELIVER_SEL(1),
+		.len = cpu_to_le16(sizeof(*frame))
+	};
+	struct net_device_stats *stats = &ndev->stats;
+	struct sk_buff *skb = priv->tx_skb;
+	struct canfd_frame *cfd;
+	struct can_frame *cf;
+	u32 txid;
+	int err;
+
+	memset(frame, 0, sizeof(*frame));
+
+	if (priv->can_idx == 0)
+		frame->tag = NCT6694_CAN_FRAME_TAG_CAN0;
+	else
+		frame->tag = NCT6694_CAN_FRAME_TAG_CAN1;
+
+	if (can_is_canfd_skb(skb)) {
+		cfd = (struct canfd_frame *)priv->tx_skb->data;
+
+		if (cfd->flags & CANFD_BRS)
+			frame->flag |= NCT6694_CAN_FRAME_FLAG_BRS;
+
+		if (cfd->can_id & CAN_EFF_FLAG) {
+			txid = cfd->can_id & CAN_EFF_MASK;
+			frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
+		} else {
+			txid = cfd->can_id & CAN_SFF_MASK;
+		}
+		frame->flag |= NCT6694_CAN_FRAME_FLAG_FD;
+		frame->id = cpu_to_le32(txid);
+		frame->length = cfd->len;
+
+		memcpy(frame->data, cfd->data, cfd->len);
+	} else {
+		cf = (struct can_frame *)priv->tx_skb->data;
+
+		if (cf->can_id & CAN_RTR_FLAG)
+			frame->flag |= NCT6694_CAN_FRAME_FLAG_RTR;
+
+		if (cf->can_id & CAN_EFF_FLAG) {
+			txid = cf->can_id & CAN_EFF_MASK;
+			frame->flag |= NCT6694_CAN_FRAME_FLAG_EFF;
+		} else {
+			txid = cf->can_id & CAN_SFF_MASK;
+		}
+		frame->id = cpu_to_le32(txid);
+		frame->length = cf->len;
+
+		memcpy(frame->data, cf->data, cf->len);
+	}
+
+	err = nct6694_write_msg(priv->nct6694, &cmd_hd, frame);
+	if (err) {
+		netdev_err(ndev, "%s: Tx FIFO full!\n", __func__);
+		can_free_echo_skb(ndev, 0, NULL);
+		stats->tx_dropped++;
+		stats->tx_errors++;
+		netif_wake_queue(ndev);
+	}
+}
+
+static void nct6694_can_tx_work(struct work_struct *work)
+{
+	struct nct6694_can_priv *priv = container_of(work,
+						     struct nct6694_can_priv,
+						     tx_work);
+	struct net_device *ndev = priv->ndev;
+
+	guard(mutex)(&priv->lock);
+
+	if (priv->tx_skb) {
+		if (priv->can.state == CAN_STATE_BUS_OFF) {
+			nct6694_can_clean(ndev);
+		} else {
+			nct6694_can_tx(ndev);
+			can_put_echo_skb(priv->tx_skb, ndev, 0, 0);
+			priv->tx_skb = NULL;
+		}
+	}
+}
+
+static netdev_tx_t nct6694_can_start_xmit(struct sk_buff *skb,
+					  struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+	if (can_dev_dropped_skb(ndev, skb))
+		return NETDEV_TX_OK;
+
+	if (priv->tx_skb) {
+		netdev_err(ndev, "hard_xmit called while tx busy\n");
+		return NETDEV_TX_BUSY;
+	}
+
+	netif_stop_queue(ndev);
+	priv->tx_skb = skb;
+	queue_work(priv->wq, &priv->tx_work);
+
+	return NETDEV_TX_OK;
+}
+
+static int nct6694_can_start(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	struct nct6694_can_setting *setting = &priv->tx->setting;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CAN_MOD,
+		.cmd = NCT6694_CAN_SETTING,
+		.sel = NCT6694_CAN_SETTING_SEL(priv->can_idx),
+		.len = cpu_to_le16(sizeof(*setting))
+	};
+	const struct can_bittiming *n_bt = &priv->can.bittiming;
+	const struct can_bittiming *d_bt = &priv->can.data_bittiming;
+	int ret;
+
+	guard(mutex)(&priv->lock);
+
+	memset(setting, 0, sizeof(*setting));
+	setting->nbr = cpu_to_le32(n_bt->bitrate);
+	setting->dbr = cpu_to_le32(d_bt->bitrate);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_MON);
+
+	if ((priv->can.ctrlmode & CAN_CTRLMODE_FD) &&
+	    priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_NISO);
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+		setting->ctrl1 |= cpu_to_le16(NCT6694_CAN_SETTING_CTRL1_LBCK);
+
+	ret = nct6694_write_msg(priv->nct6694, &cmd_hd, setting);
+	if (ret)
+		return ret;
+
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+	return ret;
+}
+
+static int nct6694_can_stop(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+
+	netif_stop_queue(ndev);
+	free_irq(ndev->irq, ndev);
+	destroy_workqueue(priv->wq);
+	priv->wq = NULL;
+	nct6694_can_clean(ndev);
+	priv->can.state = CAN_STATE_STOPPED;
+	can_rx_offload_disable(&priv->offload);
+	close_candev(ndev);
+
+	return 0;
+}
+
+static int nct6694_can_set_mode(struct net_device *ndev, enum can_mode mode)
+{
+	switch (mode) {
+	case CAN_MODE_START:
+		nct6694_can_clean(ndev);
+		nct6694_can_start(ndev);
+		netif_wake_queue(ndev);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int nct6694_can_open(struct net_device *ndev)
+{
+	struct nct6694_can_priv *priv = netdev_priv(ndev);
+	int ret;
+
+	ret = open_candev(ndev);
+	if (ret)
+		return ret;
+
+	can_rx_offload_enable(&priv->offload);
+
+	ret = request_threaded_irq(ndev->irq, NULL,
+				   nct6694_can_irq, IRQF_ONESHOT,
+				   "nct6694_can", ndev);
+	if (ret) {
+		netdev_err(ndev, "Failed to request IRQ\n");
+		goto close_candev;
+	}
+
+	priv->wq = alloc_ordered_workqueue("%s-nct6694_wq",
+					   WQ_FREEZABLE | WQ_MEM_RECLAIM,
+					   ndev->name);
+	if (!priv->wq) {
+		ret = -ENOMEM;
+		goto free_irq;
+	}
+
+	priv->tx_skb = NULL;
+
+	ret = nct6694_can_start(ndev);
+	if (ret)
+		goto destroy_wq;
+
+	netif_start_queue(ndev);
+
+	return 0;
+
+destroy_wq:
+	destroy_workqueue(priv->wq);
+free_irq:
+	free_irq(ndev->irq, ndev);
+close_candev:
+	can_rx_offload_disable(&priv->offload);
+	close_candev(ndev);
+	return ret;
+}
+
+static const struct net_device_ops nct6694_can_netdev_ops = {
+	.ndo_open = nct6694_can_open,
+	.ndo_stop = nct6694_can_stop,
+	.ndo_start_xmit = nct6694_can_start_xmit,
+	.ndo_change_mtu = can_change_mtu,
+};
+
+static const struct ethtool_ops nct6694_can_ethtool_ops = {
+	.get_ts_info = ethtool_op_get_ts_info,
+};
+
+static int nct6694_can_get_clock(struct nct6694_can_priv *priv)
+{
+	struct nct6694_can_information *info = &priv->rx->info;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_CAN_MOD,
+		.cmd = NCT6694_CAN_INFORMATION,
+		.sel = NCT6694_CAN_INFORMATION_SEL,
+		.len = cpu_to_le16(sizeof(*info))
+	};
+	int ret;
+
+	ret = nct6694_read_msg(priv->nct6694, &cmd_hd, info);
+	if (ret)
+		return ret;
+
+	return le32_to_cpu(info->can_clk);
+}
+
+static int nct6694_can_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_can_priv *priv;
+	struct net_device *ndev;
+	int ret, irq, can_clk;
+
+	irq = irq_create_mapping(nct6694->domain,
+				 NCT6694_IRQ_CAN1 + cell->id);
+	if (!irq)
+		return irq;
+
+	ndev = alloc_candev(sizeof(struct nct6694_can_priv), 1);
+	if (!ndev)
+		return -ENOMEM;
+
+	ndev->irq = irq;
+	ndev->flags |= IFF_ECHO;
+	ndev->netdev_ops = &nct6694_can_netdev_ops;
+	ndev->ethtool_ops = &nct6694_can_ethtool_ops;
+
+	priv = netdev_priv(ndev);
+	priv->nct6694 = nct6694;
+	priv->ndev = ndev;
+
+	priv->tx = devm_kzalloc(&pdev->dev, sizeof(union nct6694_can_tx),
+				GFP_KERNEL);
+	if (!priv->tx) {
+		ret = -ENOMEM;
+		goto free_candev;
+	}
+
+	priv->rx = devm_kzalloc(&pdev->dev, sizeof(union nct6694_can_rx),
+				GFP_KERNEL);
+	if (!priv->rx) {
+		ret = -ENOMEM;
+		goto free_candev;
+	}
+
+	can_clk = nct6694_can_get_clock(priv);
+	if (can_clk < 0) {
+		ret = dev_err_probe(&pdev->dev, can_clk,
+				    "Failed to get clock\n");
+		goto free_candev;
+	}
+
+	devm_mutex_init(&pdev->dev, &priv->lock);
+	INIT_WORK(&priv->tx_work, nct6694_can_tx_work);
+
+	priv->can_idx = cell->id;
+	priv->can.state = CAN_STATE_STOPPED;
+	priv->can.clock.freq = can_clk;
+	priv->can.bittiming_const = &nct6694_can_bittiming_nominal_const;
+	priv->can.data_bittiming_const = &nct6694_can_bittiming_data_const;
+	priv->can.do_set_mode = nct6694_can_set_mode;
+	priv->can.do_get_berr_counter = nct6694_can_get_berr_counter;
+
+	priv->can.ctrlmode = CAN_CTRLMODE_FD;
+
+	priv->can.ctrlmode_supported = CAN_CTRLMODE_LOOPBACK		|
+				       CAN_CTRLMODE_LISTENONLY		|
+				       CAN_CTRLMODE_FD			|
+				       CAN_CTRLMODE_FD_NON_ISO		|
+				       CAN_CTRLMODE_BERR_REPORTING;
+
+	ret = can_rx_offload_add_manual(ndev, &priv->offload,
+					NCT6694_NAPI_WEIGHT);
+	if (ret) {
+		dev_err_probe(&pdev->dev, ret, "Failed to add rx_offload\n");
+		goto free_candev;
+	}
+
+	platform_set_drvdata(pdev, priv);
+	SET_NETDEV_DEV(priv->ndev, &pdev->dev);
+
+	ret = register_candev(priv->ndev);
+	if (ret)
+		goto del_rx_offload;
+
+	return 0;
+
+del_rx_offload:
+	can_rx_offload_del(&priv->offload);
+free_candev:
+	free_candev(ndev);
+	return ret;
+}
+
+static void nct6694_can_remove(struct platform_device *pdev)
+{
+	struct nct6694_can_priv *priv = platform_get_drvdata(pdev);
+
+	cancel_work_sync(&priv->tx_work);
+	unregister_candev(priv->ndev);
+	can_rx_offload_del(&priv->offload);
+	free_candev(priv->ndev);
+}
+
+static struct platform_driver nct6694_can_driver = {
+	.driver = {
+		.name	= DRVNAME,
+	},
+	.probe		= nct6694_can_probe,
+	.remove		= nct6694_can_remove,
+};
+
+module_platform_driver(nct6694_can_driver);
+
+MODULE_DESCRIPTION("USB-CAN FD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-can");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 3/7] i2c: Add Nuvoton NCT6694 I2C support
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

This driver supports I2C adapter functionality for NCT6694 MFD
device based on USB interface, each I2C controller use default
baudrate(100K).

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS                      |   1 +
 drivers/i2c/busses/Kconfig       |  10 ++
 drivers/i2c/busses/Makefile      |   1 +
 drivers/i2c/busses/i2c-nct6694.c | 157 +++++++++++++++++++++++++++++++
 4 files changed, 169 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index f752051f4c3b..4e72f749cdf2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16722,6 +16722,7 @@ M:	Ming Yu <tmyu0@nuvoton.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	drivers/gpio/gpio-nct6694.c
+F:	drivers/i2c/busses/i2c-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	include/linux/mfd/nct6694.h
 
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index ceb3ecdf884b..a3f60e6953bd 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -1328,6 +1328,16 @@ config I2C_LJCA
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-ljca.
 
+config I2C_NCT6694
+	tristate "Nuvoton NCT6694 I2C adapter support"
+	depends on MFD_NCT6694
+	help
+	  If you say yes to this option, support will be included for Nuvoton
+	  NCT6694, a USB to I2C interface.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called i2c-nct6694.
+
 config I2C_CP2615
 	tristate "Silicon Labs CP2615 USB sound card and I2C adapter"
 	depends on USB
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 1c2a4510abe4..c5a60116dd54 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -133,6 +133,7 @@ obj-$(CONFIG_I2C_GXP)		+= i2c-gxp.o
 obj-$(CONFIG_I2C_DIOLAN_U2C)	+= i2c-diolan-u2c.o
 obj-$(CONFIG_I2C_DLN2)		+= i2c-dln2.o
 obj-$(CONFIG_I2C_LJCA)		+= i2c-ljca.o
+obj-$(CONFIG_I2C_NCT6694)	+= i2c-nct6694.o
 obj-$(CONFIG_I2C_CP2615) += i2c-cp2615.o
 obj-$(CONFIG_I2C_PARPORT)	+= i2c-parport.o
 obj-$(CONFIG_I2C_PCI1XXXX)	+= i2c-mchp-pci1xxxx.o
diff --git a/drivers/i2c/busses/i2c-nct6694.c b/drivers/i2c/busses/i2c-nct6694.c
new file mode 100644
index 000000000000..49e8110662e2
--- /dev/null
+++ b/drivers/i2c/busses/i2c-nct6694.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 I2C adapter driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * USB command module type for NCT6694 I2C controller.
+ * This defines the module type used for communication with the NCT6694
+ * I2C controller over the USB interface.
+ */
+#define NCT6694_I2C_MOD		0x03
+
+/* Command 00h - I2C Deliver */
+#define NCT6694_I2C_DELIVER	0x00
+#define NCT6694_I2C_DELIVER_SEL	0x00
+
+enum i2c_baudrate {
+	I2C_BR_25K = 0,
+	I2C_BR_50K,
+	I2C_BR_100K,
+	I2C_BR_200K,
+	I2C_BR_400K,
+	I2C_BR_800K,
+	I2C_BR_1M
+};
+
+struct __packed nct6694_i2c_deliver {
+	u8 port;
+	u8 br;
+	u8 addr;
+	u8 w_cnt;
+	u8 r_cnt;
+	u8 rsv[11];
+	u8 write_data[0x40];
+	u8 read_data[0x40];
+};
+
+struct nct6694_i2c_data {
+	struct nct6694 *nct6694;
+	struct i2c_adapter adapter;
+	struct nct6694_i2c_deliver *deliver;
+	unsigned char port;
+	unsigned char br;
+};
+
+static int nct6694_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
+{
+	struct nct6694_i2c_data *data = adap->algo_data;
+	struct nct6694_i2c_deliver *deliver = data->deliver;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_I2C_MOD,
+		.cmd = NCT6694_I2C_DELIVER,
+		.sel = NCT6694_I2C_DELIVER_SEL,
+		.len = cpu_to_le16(sizeof(*deliver))
+	};
+	int ret, i;
+
+	for (i = 0; i < num ; i++) {
+		struct i2c_msg *msg_temp = &msgs[i];
+
+		memset(deliver, 0, sizeof(*deliver));
+
+		if (msg_temp->len > 64)
+			return -EPROTO;
+
+		deliver->port = data->port;
+		deliver->br = data->br;
+		deliver->addr = i2c_8bit_addr_from_msg(msg_temp);
+		if (msg_temp->flags & I2C_M_RD) {
+			deliver->r_cnt = msg_temp->len;
+			ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+			if (ret < 0)
+				return ret;
+
+			memcpy(msg_temp->buf, deliver->read_data, msg_temp->len);
+		} else {
+			deliver->w_cnt = msg_temp->len;
+			memcpy(deliver->write_data, msg_temp->buf, msg_temp->len);
+			ret = nct6694_write_msg(data->nct6694, &cmd_hd, deliver);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return num;
+}
+
+static u32 nct6694_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm algorithm = {
+	.master_xfer = nct6694_xfer,
+	.functionality = nct6694_func,
+};
+
+static int nct6694_i2c_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_i2c_data *data;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->deliver = devm_kzalloc(&pdev->dev, sizeof(struct nct6694_i2c_deliver),
+				     GFP_KERNEL);
+	if (!data->deliver)
+		return -ENOMEM;
+
+	data->nct6694 = nct6694;
+	data->port = cell->id;
+	data->br = I2C_BR_100K;
+
+	sprintf(data->adapter.name, "NCT6694 I2C Adapter %d", cell->id);
+	data->adapter.owner = THIS_MODULE;
+	data->adapter.algo = &algorithm;
+	data->adapter.dev.parent = &pdev->dev;
+	data->adapter.algo_data = data;
+
+	platform_set_drvdata(pdev, data);
+
+	return i2c_add_adapter(&data->adapter);
+}
+
+static void nct6694_i2c_remove(struct platform_device *pdev)
+{
+	struct nct6694_i2c_data *data = platform_get_drvdata(pdev);
+
+	i2c_del_adapter(&data->adapter);
+}
+
+static struct platform_driver nct6694_i2c_driver = {
+	.driver = {
+		.name	= "nct6694-i2c",
+	},
+	.probe		= nct6694_i2c_probe,
+	.remove		= nct6694_i2c_remove,
+};
+
+module_platform_driver(nct6694_i2c_driver);
+
+MODULE_DESCRIPTION("USB-I2C adapter driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-i2c");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 2/7] gpio: Add Nuvoton NCT6694 GPIO support
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

This driver supports GPIO and IRQ functionality for NCT6694 MFD
device based on USB interface.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS                 |   1 +
 drivers/gpio/Kconfig        |  12 +
 drivers/gpio/Makefile       |   1 +
 drivers/gpio/gpio-nct6694.c | 458 ++++++++++++++++++++++++++++++++++++
 4 files changed, 472 insertions(+)
 create mode 100644 drivers/gpio/gpio-nct6694.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 79568f82b98a..f752051f4c3b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16721,6 +16721,7 @@ NUVOTON NCT6694 MFD DRIVER
 M:	Ming Yu <tmyu0@nuvoton.com>
 L:	linux-kernel@vger.kernel.org
 S:	Supported
+F:	drivers/gpio/gpio-nct6694.c
 F:	drivers/mfd/nct6694.c
 F:	include/linux/mfd/nct6694.h
 
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 93ee3aa092f8..808fbfdbff3b 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1461,6 +1461,18 @@ config GPIO_MAX77650
 	  GPIO driver for MAX77650/77651 PMIC from Maxim Semiconductor.
 	  These chips have a single pin that can be configured as GPIO.
 
+config GPIO_NCT6694
+	tristate "Nuvoton NCT6694 GPIO controller support"
+	depends on MFD_NCT6694
+	select GENERIC_IRQ_CHIP
+	select GPIOLIB_IRQCHIP
+	help
+	  This driver supports 8 GPIO pins per bank that can all be interrupt
+	  sources.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-nct6694.
+
 config GPIO_PALMAS
 	bool "TI PALMAS series PMICs GPIO"
 	depends on MFD_PALMAS
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index af3ba4d81b58..def8487540ab 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -121,6 +121,7 @@ obj-$(CONFIG_GPIO_MT7621)		+= gpio-mt7621.o
 obj-$(CONFIG_GPIO_MVEBU)		+= gpio-mvebu.o
 obj-$(CONFIG_GPIO_MXC)			+= gpio-mxc.o
 obj-$(CONFIG_GPIO_MXS)			+= gpio-mxs.o
+obj-$(CONFIG_GPIO_NCT6694)		+= gpio-nct6694.o
 obj-$(CONFIG_GPIO_NOMADIK)		+= gpio-nomadik.o
 obj-$(CONFIG_GPIO_NPCM_SGPIO)		+= gpio-npcm-sgpio.o
 obj-$(CONFIG_GPIO_OCTEON)		+= gpio-octeon.o
diff --git a/drivers/gpio/gpio-nct6694.c b/drivers/gpio/gpio-nct6694.c
new file mode 100644
index 000000000000..a3fc97a90782
--- /dev/null
+++ b/drivers/gpio/gpio-nct6694.c
@@ -0,0 +1,458 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 GPIO controller driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+/*
+ * USB command module type for NCT6694 GPIO controller.
+ * This defines the module type used for communication with the NCT6694
+ * GPIO controller over the USB interface.
+ */
+#define NCT6694_GPIO_MOD	0xFF
+
+#define NCT6694_GPIO_VER	0x90
+#define NCT6694_GPIO_VALID	0x110
+#define NCT6694_GPI_DATA	0x120
+#define NCT6694_GPO_DIR		0x170
+#define NCT6694_GPO_TYPE	0x180
+#define NCT6694_GPO_DATA	0x190
+
+#define NCT6694_GPI_STS		0x130
+#define NCT6694_GPI_CLR		0x140
+#define NCT6694_GPI_FALLING	0x150
+#define NCT6694_GPI_RISING	0x160
+
+#define NCT6694_NR_GPIO		8
+
+struct nct6694_gpio_data {
+	struct nct6694 *nct6694;
+	struct gpio_chip gpio;
+	struct mutex lock;
+	/* Protect irq operation */
+	struct mutex irq_lock;
+
+	unsigned char reg_val;
+	unsigned char irq_trig_falling;
+	unsigned char irq_trig_rising;
+
+	/* Current gpio group */
+	unsigned char group;
+};
+
+static int nct6694_get_direction(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	return !(BIT(offset) & data->reg_val);
+}
+
+static int nct6694_direction_input(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	data->reg_val &= ~BIT(offset);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_direction_output(struct gpio_chip *gpio,
+				    unsigned int offset, int val)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	/* Set direction to output */
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	data->reg_val |= BIT(offset);
+	ret = nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	/* Then set output level */
+	cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	if (val)
+		data->reg_val |= BIT(offset);
+	else
+		data->reg_val &= ~BIT(offset);
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_get_value(struct gpio_chip *gpio, unsigned int offset)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DIR + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	if (BIT(offset) & data->reg_val) {
+		cmd_hd.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group);
+		ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+		if (ret < 0)
+			return ret;
+
+		return !!(BIT(offset) & data->reg_val);
+	}
+
+	cmd_hd.offset = cpu_to_le16(NCT6694_GPI_DATA + data->group);
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	return !!(BIT(offset) & data->reg_val);
+}
+
+static void nct6694_set_value(struct gpio_chip *gpio, unsigned int offset,
+			      int val)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_DATA + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+
+	guard(mutex)(&data->lock);
+
+	nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+
+	if (val)
+		data->reg_val |= BIT(offset);
+	else
+		data->reg_val &= ~BIT(offset);
+
+	nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_set_config(struct gpio_chip *gpio, unsigned int offset,
+			      unsigned long config)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPO_TYPE + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	switch (pinconf_to_config_param(config)) {
+	case PIN_CONFIG_DRIVE_OPEN_DRAIN:
+		data->reg_val |= BIT(offset);
+		break;
+	case PIN_CONFIG_DRIVE_PUSH_PULL:
+		data->reg_val &= ~BIT(offset);
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	return nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+}
+
+static int nct6694_init_valid_mask(struct gpio_chip *gpio,
+				   unsigned long *valid_mask,
+				   unsigned int ngpios)
+{
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPIO_VALID + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret < 0)
+		return ret;
+
+	*valid_mask = data->reg_val;
+
+	return ret;
+}
+
+static irqreturn_t nct6694_irq_handler(int irq, void *priv)
+{
+	struct nct6694_gpio_data *data = priv;
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPI_STS + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	unsigned char status;
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	if (ret)
+		return IRQ_NONE;
+
+	status = data->reg_val;
+
+	while (status) {
+		int bit = __ffs(status);
+
+		data->reg_val = BIT(bit);
+		handle_nested_irq(irq_find_mapping(data->gpio.irq.domain, bit));
+		status &= ~BIT(bit);
+		cmd_hd.offset = cpu_to_le16(NCT6694_GPI_CLR + data->group);
+		nct6694_write_msg(data->nct6694, &cmd_hd, &data->reg_val);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int nct6694_get_irq_trig(struct nct6694_gpio_data *data)
+{
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+	int ret;
+
+	guard(mutex)(&data->lock);
+
+	ret = nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+	if (ret)
+		return ret;
+
+	cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+	return nct6694_read_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+}
+
+static void nct6694_irq_mask(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	gpiochip_disable_irq(gpio, hwirq);
+}
+
+static void nct6694_irq_unmask(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	gpiochip_enable_irq(gpio, hwirq);
+}
+
+static int nct6694_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+
+	guard(mutex)(&data->lock);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_RISING:
+		data->irq_trig_rising |= BIT(hwirq);
+		break;
+
+	case IRQ_TYPE_EDGE_FALLING:
+		data->irq_trig_falling |= BIT(hwirq);
+		break;
+
+	case IRQ_TYPE_EDGE_BOTH:
+		data->irq_trig_rising |= BIT(hwirq);
+		data->irq_trig_falling |= BIT(hwirq);
+		break;
+
+	default:
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+static void nct6694_irq_bus_lock(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+
+	mutex_lock(&data->irq_lock);
+}
+
+static void nct6694_irq_bus_sync_unlock(struct irq_data *d)
+{
+	struct gpio_chip *gpio = irq_data_get_irq_chip_data(d);
+	struct nct6694_gpio_data *data = gpiochip_get_data(gpio);
+	struct nct6694_cmd_header cmd_hd = {
+		.mod = NCT6694_GPIO_MOD,
+		.offset = cpu_to_le16(NCT6694_GPI_FALLING + data->group),
+		.len = cpu_to_le16(sizeof(data->reg_val))
+	};
+
+	scoped_guard(mutex, &data->lock) {
+		nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_falling);
+
+		cmd_hd.offset = cpu_to_le16(NCT6694_GPI_RISING + data->group);
+		nct6694_write_msg(data->nct6694, &cmd_hd, &data->irq_trig_rising);
+	}
+
+	mutex_unlock(&data->irq_lock);
+}
+
+static const struct irq_chip nct6694_irq_chip = {
+	.name			= "nct6694-gpio",
+	.irq_mask		= nct6694_irq_mask,
+	.irq_unmask		= nct6694_irq_unmask,
+	.irq_set_type		= nct6694_irq_set_type,
+	.irq_bus_lock		= nct6694_irq_bus_lock,
+	.irq_bus_sync_unlock	= nct6694_irq_bus_sync_unlock,
+	.flags			= IRQCHIP_IMMUTABLE,
+	GPIOCHIP_IRQ_RESOURCE_HELPERS,
+};
+
+static int nct6694_gpio_probe(struct platform_device *pdev)
+{
+	const struct mfd_cell *cell = mfd_get_cell(pdev);
+	struct device *dev = &pdev->dev;
+	struct nct6694 *nct6694 = dev_get_drvdata(pdev->dev.parent);
+	struct nct6694_gpio_data *data;
+	struct gpio_irq_chip *girq;
+	int ret, irq, i;
+	char **names;
+
+	irq = irq_create_mapping(nct6694->domain,
+				 NCT6694_IRQ_GPIO0 + cell->id);
+	if (!irq)
+		return -EINVAL;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	names = devm_kcalloc(dev, NCT6694_NR_GPIO, sizeof(char *),
+			     GFP_KERNEL);
+	if (!names)
+		return -ENOMEM;
+
+	for (i = 0; i < NCT6694_NR_GPIO; i++) {
+		names[i] = devm_kasprintf(dev, GFP_KERNEL, "GPIO%X%d",
+					  cell->id, i);
+		if (!names[i])
+			return -ENOMEM;
+	}
+
+	data->nct6694 = nct6694;
+	data->group = cell->id;
+
+	data->gpio.names		= (const char * const*)names;
+	data->gpio.label		= pdev->name;
+	data->gpio.direction_input	= nct6694_direction_input;
+	data->gpio.get			= nct6694_get_value;
+	data->gpio.direction_output	= nct6694_direction_output;
+	data->gpio.set			= nct6694_set_value;
+	data->gpio.get_direction	= nct6694_get_direction;
+	data->gpio.set_config		= nct6694_set_config;
+	data->gpio.init_valid_mask	= nct6694_init_valid_mask;
+	data->gpio.base			= -1;
+	data->gpio.can_sleep		= false;
+	data->gpio.owner		= THIS_MODULE;
+	data->gpio.ngpio		= NCT6694_NR_GPIO;
+
+	devm_mutex_init(dev, &data->lock);
+	devm_mutex_init(dev, &data->irq_lock);
+
+	ret = nct6694_get_irq_trig(data);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to get irq trigger type\n");
+
+	girq = &data->gpio.irq;
+	gpio_irq_chip_set_chip(girq, &nct6694_irq_chip);
+	girq->parent_handler = NULL;
+	girq->num_parents = 0;
+	girq->parents = NULL;
+	girq->default_type = IRQ_TYPE_NONE;
+	girq->handler = handle_level_irq;
+	girq->threaded = true;
+
+	ret = devm_request_threaded_irq(dev, irq, NULL, nct6694_irq_handler,
+					IRQF_ONESHOT | IRQF_SHARED,
+					"nct6694-gpio", data);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to request irq\n");
+
+	return devm_gpiochip_add_data(dev, &data->gpio, data);
+}
+
+static struct platform_driver nct6694_gpio_driver = {
+	.driver = {
+		.name	= "nct6694-gpio",
+	},
+	.probe		= nct6694_gpio_probe,
+};
+
+module_platform_driver(nct6694_gpio_driver);
+
+MODULE_DESCRIPTION("USB-GPIO controller driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:nct6694-gpio");
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 1/7] mfd: Add core driver for Nuvoton NCT6694
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu
In-Reply-To: <20250114033010.2445925-1-a0282524688@gmail.com>

The Nuvoton NCT6694 is a peripheral expander with 16 GPIO chips,
6 I2C controllers, 2 CANfd controllers, 2 Watchdog timers, ADC,
PWM, and RTC.

This driver implements USB device functionality and shares the
chip's peripherals as a child device.

Each child device can use the USB functions nct6694_read_msg()
and nct6694_write_msg() to issue a command. They can also request
interrupt that will be called when the USB device receives its
interrupt pipe.

Signed-off-by: Ming Yu <a0282524688@gmail.com>
---
 MAINTAINERS                 |   7 +
 drivers/mfd/Kconfig         |  10 +
 drivers/mfd/Makefile        |   2 +
 drivers/mfd/nct6694.c       | 388 ++++++++++++++++++++++++++++++++++++
 include/linux/mfd/nct6694.h | 109 ++++++++++
 5 files changed, 516 insertions(+)
 create mode 100644 drivers/mfd/nct6694.c
 create mode 100644 include/linux/mfd/nct6694.h

diff --git a/MAINTAINERS b/MAINTAINERS
index a87ddad78e26..79568f82b98a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16717,6 +16717,13 @@ F:	drivers/nubus/
 F:	include/linux/nubus.h
 F:	include/uapi/linux/nubus.h
 
+NUVOTON NCT6694 MFD DRIVER
+M:	Ming Yu <tmyu0@nuvoton.com>
+L:	linux-kernel@vger.kernel.org
+S:	Supported
+F:	drivers/mfd/nct6694.c
+F:	include/linux/mfd/nct6694.h
+
 NVIDIA (rivafb and nvidiafb) FRAMEBUFFER DRIVER
 M:	Antonino Daplas <adaplas@gmail.com>
 L:	linux-fbdev@vger.kernel.org
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index ae23b317a64e..3e641bfeeb38 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1045,6 +1045,16 @@ config MFD_MENF21BMC
 	  This driver can also be built as a module. If so the module
 	  will be called menf21bmc.
 
+config MFD_NCT6694
+	tristate "Nuvoton NCT6694 support"
+	select MFD_CORE
+	depends on USB
+	help
+	  This adds support for Nuvoton USB device NCT6694 sharing peripherals
+	  This includes the USB device driver and core APIs.
+	  Additional drivers must be enabled in order to use the functionality
+	  of the device.
+
 config MFD_OCELOT
 	tristate "Microsemi Ocelot External Control Support"
 	depends on SPI_MASTER
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e057d6d6faef..3c902d3704dc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -121,6 +121,8 @@ obj-$(CONFIG_MFD_MC13XXX)	+= mc13xxx-core.o
 obj-$(CONFIG_MFD_MC13XXX_SPI)	+= mc13xxx-spi.o
 obj-$(CONFIG_MFD_MC13XXX_I2C)	+= mc13xxx-i2c.o
 
+obj-$(CONFIG_MFD_NCT6694)	+= nct6694.o
+
 obj-$(CONFIG_MFD_CORE)		+= mfd-core.o
 
 ocelot-soc-objs			:= ocelot-core.o ocelot-spi.o
diff --git a/drivers/mfd/nct6694.c b/drivers/mfd/nct6694.c
new file mode 100644
index 000000000000..294b6b7a902e
--- /dev/null
+++ b/drivers/mfd/nct6694.c
@@ -0,0 +1,388 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Nuvoton NCT6694 MFD driver based on USB interface.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#include <linux/bits.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/nct6694.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#define MFD_DEV_SIMPLE(_name)				\
+{							\
+	.name = NCT6694_DEV_##_name,			\
+}							\
+
+#define MFD_DEV_WITH_ID(_name, _id)			\
+{							\
+	.name = NCT6694_DEV_##_name,			\
+	.id = _id,					\
+}
+
+/* MFD device resources */
+static const struct mfd_cell nct6694_dev[] = {
+	MFD_DEV_WITH_ID(GPIO, 0x0),
+	MFD_DEV_WITH_ID(GPIO, 0x1),
+	MFD_DEV_WITH_ID(GPIO, 0x2),
+	MFD_DEV_WITH_ID(GPIO, 0x3),
+	MFD_DEV_WITH_ID(GPIO, 0x4),
+	MFD_DEV_WITH_ID(GPIO, 0x5),
+	MFD_DEV_WITH_ID(GPIO, 0x6),
+	MFD_DEV_WITH_ID(GPIO, 0x7),
+	MFD_DEV_WITH_ID(GPIO, 0x8),
+	MFD_DEV_WITH_ID(GPIO, 0x9),
+	MFD_DEV_WITH_ID(GPIO, 0xA),
+	MFD_DEV_WITH_ID(GPIO, 0xB),
+	MFD_DEV_WITH_ID(GPIO, 0xC),
+	MFD_DEV_WITH_ID(GPIO, 0xD),
+	MFD_DEV_WITH_ID(GPIO, 0xE),
+	MFD_DEV_WITH_ID(GPIO, 0xF),
+
+	MFD_DEV_WITH_ID(I2C, 0x0),
+	MFD_DEV_WITH_ID(I2C, 0x1),
+	MFD_DEV_WITH_ID(I2C, 0x2),
+	MFD_DEV_WITH_ID(I2C, 0x3),
+	MFD_DEV_WITH_ID(I2C, 0x4),
+	MFD_DEV_WITH_ID(I2C, 0x5),
+
+	MFD_DEV_WITH_ID(CAN, 0x0),
+	MFD_DEV_WITH_ID(CAN, 0x1),
+
+	MFD_DEV_WITH_ID(WDT, 0x0),
+	MFD_DEV_WITH_ID(WDT, 0x1),
+
+	MFD_DEV_SIMPLE(HWMON),
+	MFD_DEV_SIMPLE(RTC),
+};
+
+static int nct6694_response_err_handling(struct nct6694 *nct6694,
+					 unsigned char err_status)
+{
+	struct device *dev = &nct6694->udev->dev;
+
+	switch (err_status) {
+	case NCT6694_NO_ERROR:
+		return err_status;
+	case NCT6694_NOT_SUPPORT_ERROR:
+		dev_dbg(dev, "%s: Command is not supported!\n", __func__);
+		break;
+	case NCT6694_NO_RESPONSE_ERROR:
+		dev_dbg(dev, "%s: Command received no response!\n", __func__);
+		break;
+	case NCT6694_TIMEOUT_ERROR:
+		dev_dbg(dev, "%s: Command timed out!\n", __func__);
+		break;
+	case NCT6694_PENDING:
+		dev_dbg(dev, "%s: Command is pending!\n", __func__);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return -EIO;
+}
+
+int nct6694_read_msg(struct nct6694 *nct6694,
+		     struct nct6694_cmd_header *cmd_hd,
+		     void *buf)
+{
+	union nct6694_usb_msg *msg = nct6694->usb_msg;
+	int tx_len, rx_len, ret;
+
+	guard(mutex)(&nct6694->access_lock);
+
+	/* Send command packet to USB device */
+	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+	msg->cmd_header.hctrl = NCT6694_HCTRL_GET;
+
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
+			   &msg->cmd_header, sizeof(*msg), &tx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Receive response packet from USB device */
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+			   &msg->response_header, sizeof(*msg), &rx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Receive data packet from USB device */
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+			   buf, le16_to_cpu(cmd_hd->len), &rx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	if (rx_len != le16_to_cpu(cmd_hd->len)) {
+		dev_dbg(&nct6694->udev->dev, "%s: Received length is not match!\n",
+			__func__);
+		return -EIO;
+	}
+
+	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL(nct6694_read_msg);
+
+int nct6694_write_msg(struct nct6694 *nct6694, struct nct6694_cmd_header *cmd_hd,
+		      void *buf)
+{
+	union nct6694_usb_msg *msg = nct6694->usb_msg;
+	int tx_len, rx_len, ret;
+
+	guard(mutex)(&nct6694->access_lock);
+
+	/* Send command packet to USB device */
+	memcpy(&msg->cmd_header, cmd_hd, sizeof(*cmd_hd));
+	msg->cmd_header.hctrl = NCT6694_HCTRL_SET;
+
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
+			   &msg->cmd_header, sizeof(*msg), &tx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Send data packet to USB device */
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_sndbulkpipe(nct6694->udev, NCT6694_BULK_OUT_EP),
+			   buf, le16_to_cpu(cmd_hd->len), &tx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Receive response packet from USB device */
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+			   &msg->response_header, sizeof(*msg), &rx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	/* Receive data packet from USB device */
+	ret = usb_bulk_msg(nct6694->udev,
+			   usb_rcvbulkpipe(nct6694->udev, NCT6694_BULK_IN_EP),
+			   buf, le16_to_cpu(cmd_hd->len), &rx_len,
+			   nct6694->timeout);
+	if (ret)
+		return ret;
+
+	if (rx_len != le16_to_cpu(cmd_hd->len)) {
+		dev_dbg(&nct6694->udev->dev, "%s: Sent length is not match!\n",
+			__func__);
+		return -EIO;
+	}
+
+	return nct6694_response_err_handling(nct6694, msg->response_header.sts);
+}
+EXPORT_SYMBOL(nct6694_write_msg);
+
+static void usb_int_callback(struct urb *urb)
+{
+	struct nct6694 *nct6694 = urb->context;
+	struct device *dev = &nct6694->udev->dev;
+	unsigned int *int_status = urb->transfer_buffer;
+	int ret;
+
+	switch (urb->status) {
+	case 0:
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:
+		goto resubmit;
+	}
+
+	while (*int_status) {
+		int irq = __ffs(*int_status);
+
+		generic_handle_irq_safe(irq_find_mapping(nct6694->domain, irq));
+		*int_status &= ~BIT(irq);
+	}
+
+resubmit:
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret)
+		dev_dbg(dev, "%s: Failed to resubmit urb, status %pe",
+			__func__, ERR_PTR(ret));
+}
+
+static void nct6694_irq_lock(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+
+	mutex_lock(&nct6694->irq_lock);
+}
+
+static void nct6694_irq_sync_unlock(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+
+	mutex_unlock(&nct6694->irq_lock);
+}
+
+static void nct6694_irq_enable(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+	irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+	nct6694->irq_enable |= BIT(hwirq);
+}
+
+static void nct6694_irq_disable(struct irq_data *data)
+{
+	struct nct6694 *nct6694 = irq_data_get_irq_chip_data(data);
+	irq_hw_number_t hwirq = irqd_to_hwirq(data);
+
+	nct6694->irq_enable &= ~BIT(hwirq);
+}
+
+static struct irq_chip nct6694_irq_chip = {
+	.name = "nct6694-irq",
+	.flags = IRQCHIP_SKIP_SET_WAKE,
+	.irq_bus_lock = nct6694_irq_lock,
+	.irq_bus_sync_unlock = nct6694_irq_sync_unlock,
+	.irq_enable = nct6694_irq_enable,
+	.irq_disable = nct6694_irq_disable,
+};
+
+static int nct6694_irq_domain_map(struct irq_domain *d, unsigned int irq,
+				  irq_hw_number_t hw)
+{
+	struct nct6694 *nct6694 = d->host_data;
+
+	irq_set_chip_data(irq, nct6694);
+	irq_set_chip_and_handler(irq, &nct6694_irq_chip, handle_simple_irq);
+
+	return 0;
+}
+
+static void nct6694_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
+{
+	irq_set_chip_and_handler(irq, NULL, NULL);
+	irq_set_chip_data(irq, NULL);
+}
+
+static const struct irq_domain_ops nct6694_irq_domain_ops = {
+	.map	= nct6694_irq_domain_map,
+	.unmap	= nct6694_irq_domain_unmap,
+};
+
+static int nct6694_usb_probe(struct usb_interface *iface,
+			     const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+	struct usb_endpoint_descriptor *int_endpoint;
+	struct usb_host_interface *interface;
+	struct device *dev = &udev->dev;
+	struct nct6694 *nct6694;
+	int pipe, maxp;
+	int ret;
+
+	interface = iface->cur_altsetting;
+
+	int_endpoint = &interface->endpoint[0].desc;
+	if (!usb_endpoint_is_int_in(int_endpoint))
+		return -ENODEV;
+
+	nct6694 = devm_kzalloc(dev, sizeof(*nct6694), GFP_KERNEL);
+	if (!nct6694)
+		return -ENOMEM;
+
+	pipe = usb_rcvintpipe(udev, NCT6694_INT_IN_EP);
+	maxp = usb_maxpacket(udev, pipe);
+
+	nct6694->usb_msg = devm_kzalloc(dev, sizeof(union nct6694_usb_msg),
+					GFP_KERNEL);
+	if (!nct6694->usb_msg)
+		return -ENOMEM;
+
+	nct6694->int_buffer = devm_kzalloc(dev, maxp, GFP_KERNEL);
+	if (!nct6694->int_buffer)
+		return -ENOMEM;
+
+	nct6694->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!nct6694->int_in_urb)
+		return -ENOMEM;
+
+	nct6694->domain = irq_domain_add_simple(NULL, NCT6694_NR_IRQS, 0,
+						&nct6694_irq_domain_ops,
+						nct6694);
+	if (!nct6694->domain) {
+		ret = -ENODEV;
+		goto err_urb;
+	}
+
+	nct6694->udev = udev;
+	nct6694->timeout = NCT6694_URB_TIMEOUT;	/* Wait until urb complete */
+
+	devm_mutex_init(dev, &nct6694->access_lock);
+	devm_mutex_init(dev, &nct6694->irq_lock);
+
+	usb_fill_int_urb(nct6694->int_in_urb, udev, pipe,
+			 nct6694->int_buffer, maxp, usb_int_callback,
+			 nct6694, int_endpoint->bInterval);
+	ret = usb_submit_urb(nct6694->int_in_urb, GFP_KERNEL);
+	if (ret)
+		goto err_urb;
+
+	dev_set_drvdata(dev, nct6694);
+	usb_set_intfdata(iface, nct6694);
+
+	ret = mfd_add_hotplug_devices(dev, nct6694_dev, ARRAY_SIZE(nct6694_dev));
+	if (ret)
+		goto err_mfd;
+
+	return 0;
+
+err_mfd:
+	usb_kill_urb(nct6694->int_in_urb);
+err_urb:
+	usb_free_urb(nct6694->int_in_urb);
+	return ret;
+}
+
+static void nct6694_usb_disconnect(struct usb_interface *iface)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+	struct nct6694 *nct6694 = usb_get_intfdata(iface);
+
+	mfd_remove_devices(&udev->dev);
+	usb_kill_urb(nct6694->int_in_urb);
+	usb_free_urb(nct6694->int_in_urb);
+}
+
+static const struct usb_device_id nct6694_ids[] = {
+	{ USB_DEVICE_AND_INTERFACE_INFO(NCT6694_VENDOR_ID,
+					NCT6694_PRODUCT_ID,
+					0xFF, 0x00, 0x00)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, nct6694_ids);
+
+static struct usb_driver nct6694_usb_driver = {
+	.name	= "nct6694",
+	.id_table = nct6694_ids,
+	.probe = nct6694_usb_probe,
+	.disconnect = nct6694_usb_disconnect,
+};
+
+module_usb_driver(nct6694_usb_driver);
+
+MODULE_DESCRIPTION("USB-MFD driver for NCT6694");
+MODULE_AUTHOR("Ming Yu <tmyu0@nuvoton.com>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/nct6694.h b/include/linux/mfd/nct6694.h
new file mode 100644
index 000000000000..67ca835589ad
--- /dev/null
+++ b/include/linux/mfd/nct6694.h
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Nuvoton NCT6694 USB transaction and data structure.
+ *
+ * Copyright (C) 2024 Nuvoton Technology Corp.
+ */
+
+#ifndef __MFD_NCT6694_H
+#define __MFD_NCT6694_H
+
+#define NCT6694_DEV_GPIO	"nct6694-gpio"
+#define NCT6694_DEV_I2C		"nct6694-i2c"
+#define NCT6694_DEV_CAN		"nct6694-can"
+#define NCT6694_DEV_WDT		"nct6694-wdt"
+#define NCT6694_DEV_HWMON	"nct6694-hwmon"
+#define NCT6694_DEV_RTC		"nct6694-rtc"
+
+#define NCT6694_VENDOR_ID	0x0416
+#define NCT6694_PRODUCT_ID	0x200B
+#define NCT6694_INT_IN_EP	0x81
+#define NCT6694_BULK_IN_EP	0x02
+#define NCT6694_BULK_OUT_EP	0x03
+
+#define NCT6694_HCTRL_SET	0x40
+#define NCT6694_HCTRL_GET	0x80
+
+#define NCT6694_URB_TIMEOUT	1000
+
+enum nct6694_irq_id {
+	NCT6694_IRQ_GPIO0 = 0,
+	NCT6694_IRQ_GPIO1,
+	NCT6694_IRQ_GPIO2,
+	NCT6694_IRQ_GPIO3,
+	NCT6694_IRQ_GPIO4,
+	NCT6694_IRQ_GPIO5,
+	NCT6694_IRQ_GPIO6,
+	NCT6694_IRQ_GPIO7,
+	NCT6694_IRQ_GPIO8,
+	NCT6694_IRQ_GPIO9,
+	NCT6694_IRQ_GPIOA,
+	NCT6694_IRQ_GPIOB,
+	NCT6694_IRQ_GPIOC,
+	NCT6694_IRQ_GPIOD,
+	NCT6694_IRQ_GPIOE,
+	NCT6694_IRQ_GPIOF,
+	NCT6694_IRQ_CAN1,
+	NCT6694_IRQ_CAN2,
+	NCT6694_IRQ_RTC,
+	NCT6694_NR_IRQS,
+};
+
+enum nct6694_response_err_status {
+	NCT6694_NO_ERROR = 0,
+	NCT6694_FORMAT_ERROR,
+	NCT6694_RESERVED1,
+	NCT6694_RESERVED2,
+	NCT6694_NOT_SUPPORT_ERROR,
+	NCT6694_NO_RESPONSE_ERROR,
+	NCT6694_TIMEOUT_ERROR,
+	NCT6694_PENDING,
+};
+
+struct __packed nct6694_cmd_header {
+	u8 rsv1;
+	u8 mod;
+	union __packed {
+		__le16 offset;
+		struct __packed {
+			u8 cmd;
+			u8 sel;
+		};
+	};
+	u8 hctrl;
+	u8 rsv2;
+	__le16 len;
+};
+
+struct __packed nct6694_response_header {
+	u8 sequence_id;
+	u8 sts;
+	u8 reserved[4];
+	__le16 len;
+};
+
+union __packed nct6694_usb_msg {
+	struct nct6694_cmd_header cmd_header;
+	struct nct6694_response_header response_header;
+};
+
+struct nct6694 {
+	struct usb_device *udev;
+	struct urb *int_in_urb;
+	struct irq_domain *domain;
+	struct mutex access_lock;
+	struct mutex irq_lock;
+	union nct6694_usb_msg *usb_msg;
+	unsigned char *int_buffer;
+	unsigned int irq_enable;
+	/* time in msec to wait for the urb to the complete */
+	long timeout;
+};
+
+int nct6694_read_msg(struct nct6694 *nct6694, struct nct6694_cmd_header *cmd_hd,
+		     void *buf);
+
+int nct6694_write_msg(struct nct6694 *nct6694, struct nct6694_cmd_header *cmd_hd,
+		      void *buf);
+
+#endif
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 0/7] Add Nuvoton NCT6694 MFD drivers
From: Ming Yu @ 2025-01-14  3:30 UTC (permalink / raw)
  To: tmyu0, lee, linus.walleij, brgl, andi.shyti, mkl, mailhol.vincent,
	andrew+netdev, davem, edumazet, kuba, pabeni, wim, linux,
	jdelvare, alexandre.belloni
  Cc: linux-kernel, linux-gpio, linux-i2c, linux-can, netdev,
	linux-watchdog, linux-hwmon, linux-rtc, linux-usb, Ming Yu

This patch series introduces support for Nuvoton NCT6694, a peripheral
expander based on USB interface. It models the chip as an MFD driver
(1/7), GPIO driver(2/7), I2C Adapter driver(3/7), CANfd driver(4/7),
WDT driver(5/7), HWMON driver(6/7), and RTC driver(7/7).

The MFD driver implements USB device functionality to issue
custom-define USB bulk pipe packets for NCT6694. Each child device can
use the USB functions nct6694_read_msg() and nct6694_write_msg() to issue
a command. They can also request interrupt that will be called when the
USB device receives its interrupt pipe.

The following introduces the custom-define USB transactions:
	nct6694_read_msg - Send bulk-out pipe to write request packet
			   Receive bulk-in pipe to read response packet
			   Receive bulk-in pipe to read data packet

	nct6694_write_msg - Send bulk-out pipe to write request packet
			    Send bulk-out pipe to write data packet
			    Receive bulk-in pipe to read response packet
			    Receive bulk-in pipe to read data packet

Changes since version 4:
- Modify arguments in read/write function to a pointer to cmd_header
- Modify all callers that call the read/write function
- Move the nct6694_canfd.c to drivers/net/can/usb/
- Fix the missing rx offload function in nct6694_canfd.c
- Fix warngings in nct6694-hwmon.c

Changes since version 3:
- Modify array buffer to structure for each drivers
- Fix defines and comments for each drivers
- Add header <linux/bits.h> and use BIT macro in nct6694.c and
  gpio-nct6694.c
- Modify mutex_init() to devm_mutex_init()
- Add rx-offload helper in nct6694_canfd.c
- Drop watchdog_init_timeout() in nct6694_wdt.c
- Modify the division method to DIV_ROUND_CLOSEST() in nct6694-hwmon.c
- Drop private mutex and use rtc core lock in rtc-nct6694.c
- Modify device_set_wakeup_capable() to device_init_wakeup() in
  rtc-nct6694.c

Changes since version 2:
- Add MODULE_ALIAS() for each child driver
- Modify gpio line names be a local variable in gpio-nct6694.c
- Drop unnecessary platform_get_drvdata() in gpio-nct6694.c
- Rename each command in nct6694_canfd.c
- Modify each function name consistently in nct6694_canfd.c
- Modify the pretimeout validation procedure in nct6694_wdt.c
- Fix warnings in nct6694-hwmon.c

Changes since version 1:
- Implement IRQ domain to handle IRQ demux in nct6694.c
- Modify USB_DEVICE to USB_DEVICE_AND_INTERFACE_INFO API in nct6694.c
- Add each driver's command structure
- Fix USB functions in nct6694.c
- Fix platform driver registration in each child driver
- Sort each driver's header files alphabetically
- Drop unnecessary header in gpio-nct6694.c
- Add gpio line names in gpio-nct6694.c
- Fix errors and warnings in nct6694_canfd.c
- Fix TX-flow control in nct6694_canfd.c
- Fix warnings in nct6694_wdt.c
- Drop unnecessary logs in nct6694_wdt.c
- Modify start() function to setup device in nct6694_wdt.c
- Add voltage sensors functionality in nct6694-hwmon.c
- Add temperature sensors functionality in nct6694-hwmon.c
- Fix overwrite error return values in nct6694-hwmon.c
- Add write value limitation for each write() function in nct6694-hwmon.c
- Drop unnecessary logs in rtc-nct6694.c
- Fix overwrite error return values in rtc-nct6694.c
- Modify to use dev_err_probe API in rtc-nct6694.c


Ming Yu (7):
  mfd: Add core driver for Nuvoton NCT6694
  gpio: Add Nuvoton NCT6694 GPIO support
  i2c: Add Nuvoton NCT6694 I2C support
  can: Add Nuvoton NCT6694 CAN support
  watchdog: Add Nuvoton NCT6694 WDT support
  hwmon: Add Nuvoton NCT6694 HWMON support
  rtc: Add Nuvoton NCT6694 RTC support

 MAINTAINERS                         |  13 +
 drivers/gpio/Kconfig                |  12 +
 drivers/gpio/Makefile               |   1 +
 drivers/gpio/gpio-nct6694.c         | 458 ++++++++++++++
 drivers/hwmon/Kconfig               |  10 +
 drivers/hwmon/Makefile              |   1 +
 drivers/hwmon/nct6694-hwmon.c       | 947 ++++++++++++++++++++++++++++
 drivers/i2c/busses/Kconfig          |  10 +
 drivers/i2c/busses/Makefile         |   1 +
 drivers/i2c/busses/i2c-nct6694.c    | 157 +++++
 drivers/mfd/Kconfig                 |  10 +
 drivers/mfd/Makefile                |   2 +
 drivers/mfd/nct6694.c               | 388 ++++++++++++
 drivers/net/can/usb/Kconfig         |  10 +
 drivers/net/can/usb/Makefile        |   1 +
 drivers/net/can/usb/nct6694_canfd.c | 856 +++++++++++++++++++++++++
 drivers/rtc/Kconfig                 |  10 +
 drivers/rtc/Makefile                |   1 +
 drivers/rtc/rtc-nct6694.c           | 286 +++++++++
 drivers/watchdog/Kconfig            |  11 +
 drivers/watchdog/Makefile           |   1 +
 drivers/watchdog/nct6694_wdt.c      | 295 +++++++++
 include/linux/mfd/nct6694.h         | 109 ++++
 23 files changed, 3590 insertions(+)
 create mode 100644 drivers/gpio/gpio-nct6694.c
 create mode 100644 drivers/hwmon/nct6694-hwmon.c
 create mode 100644 drivers/i2c/busses/i2c-nct6694.c
 create mode 100644 drivers/mfd/nct6694.c
 create mode 100644 drivers/net/can/usb/nct6694_canfd.c
 create mode 100644 drivers/rtc/rtc-nct6694.c
 create mode 100644 drivers/watchdog/nct6694_wdt.c
 create mode 100644 include/linux/mfd/nct6694.h

-- 
2.34.1


^ permalink raw reply

* Re: [PATCH net-next] can: grcan: move napi_enable() from under spin lock
From: patchwork-bot+netdevbpf @ 2025-01-14  3:30 UTC (permalink / raw)
  To: Jakub Kicinski
  Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, mkl,
	mailhol.vincent, linux-can
In-Reply-To: <20250111024742.3680902-1-kuba@kernel.org>

Hello:

This patch was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Fri, 10 Jan 2025 18:47:42 -0800 you wrote:
> I don't see any reason why napi_enable() needs to be under the lock,
> only reason I could think of is if the IRQ also took this lock
> but it doesn't. napi_enable() will soon need to sleep.
> 
> Signed-off-by: Jakub Kicinski <kuba@kernel.org>
> ---
> Marc, if this is correct is it okay for me to take via net-next
> directly? I have a bunch of patches which depend on it.
> 
> [...]

Here is the summary with links:
  - [net-next] can: grcan: move napi_enable() from under spin lock
    https://git.kernel.org/netdev/net-next/c/7c125d5b767b

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH net-next v8 01/11] rtnetlink: Lookup device in target netns when creating link
From: Kuniyuki Iwashima @ 2025-01-14  1:39 UTC (permalink / raw)
  To: shaw.leon
  Cc: alex.aring, andrew+netdev, b.a.t.m.a.n, bpf, bridge, davem,
	donald.hunter, dsahern, edumazet, herbert, horms, kuba, kuniyu,
	linux-can, linux-kernel, linux-kselftest, linux-ppp, linux-rdma,
	linux-wireless, linux-wpan, miquel.raynal, netdev,
	osmocom-net-gprs, pabeni, shuah, stefan, steffen.klassert,
	wireguard
In-Reply-To: <20250113143719.7948-2-shaw.leon@gmail.com>

From: Xiao Liang <shaw.leon@gmail.com>
Date: Mon, 13 Jan 2025 22:37:09 +0800
> When creating link, lookup for existing device in target net namespace
> instead of current one.
> For example, two links created by:
> 
>   # ip link add dummy1 type dummy
>   # ip link add netns ns1 dummy1 type dummy
> 
> should have no conflict since they are in different namespaces.
> 
> Signed-off-by: Xiao Liang <shaw.leon@gmail.com>

Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>

^ permalink raw reply

* [PATCH net-next v8 11/11] selftests: net: Add test cases for link and peer netns
From: Xiao Liang @ 2025-01-13 14:37 UTC (permalink / raw)
  To: netdev, linux-kselftest, Kuniyuki Iwashima, Jakub Kicinski
  Cc: David S. Miller, David Ahern, Eric Dumazet, Paolo Abeni,
	Andrew Lunn, Simon Horman, Shuah Khan, Donald Hunter,
	Alexander Aring, Stefan Schmidt, Miquel Raynal, Steffen Klassert,
	Herbert Xu, linux-rdma, linux-can, osmocom-net-gprs, bpf,
	linux-ppp, wireguard, linux-wireless, b.a.t.m.a.n, bridge,
	linux-wpan, linux-kernel
In-Reply-To: <20250113143719.7948-1-shaw.leon@gmail.com>

 - Add test for creating link in another netns when a link of the same
   name and ifindex exists in current netns.
 - Add test to verify that link is created in target netns directly -
   no link new/del events should be generated in link netns or current
   netns.
 - Add test cases to verify that link-netns is set as expected for
   various drivers and combination of namespace-related parameters.

Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
---
 tools/testing/selftests/net/Makefile      |   1 +
 tools/testing/selftests/net/config        |   5 +
 tools/testing/selftests/net/link_netns.py | 141 ++++++++++++++++++++++
 tools/testing/selftests/net/netns-name.sh |  10 ++
 4 files changed, 157 insertions(+)
 create mode 100755 tools/testing/selftests/net/link_netns.py

diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 73ee88d6b043..df07a38f884f 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -35,6 +35,7 @@ TEST_PROGS += cmsg_so_mark.sh
 TEST_PROGS += cmsg_so_priority.sh
 TEST_PROGS += cmsg_time.sh cmsg_ipv6.sh
 TEST_PROGS += netns-name.sh
+TEST_PROGS += link_netns.py
 TEST_PROGS += nl_netdev.py
 TEST_PROGS += srv6_end_dt46_l3vpn_test.sh
 TEST_PROGS += srv6_end_dt4_l3vpn_test.sh
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 5b9baf708950..ab55270669ec 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -107,3 +107,8 @@ CONFIG_XFRM_INTERFACE=m
 CONFIG_XFRM_USER=m
 CONFIG_IP_NF_MATCH_RPFILTER=m
 CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IPVLAN=m
+CONFIG_CAN=m
+CONFIG_CAN_DEV=m
+CONFIG_CAN_VXCAN=m
+CONFIG_NETKIT=y
diff --git a/tools/testing/selftests/net/link_netns.py b/tools/testing/selftests/net/link_netns.py
new file mode 100755
index 000000000000..aab043c59d69
--- /dev/null
+++ b/tools/testing/selftests/net/link_netns.py
@@ -0,0 +1,141 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+
+import time
+
+from lib.py import ksft_run, ksft_exit, ksft_true
+from lib.py import ip
+from lib.py import NetNS, NetNSEnter
+from lib.py import RtnlFamily
+
+
+LINK_NETNSID = 100
+
+
+def test_event() -> None:
+    with NetNS() as ns1, NetNS() as ns2:
+        with NetNSEnter(str(ns2)):
+            rtnl = RtnlFamily()
+
+        rtnl.ntf_subscribe("rtnlgrp-link")
+
+        ip(f"netns set {ns2} {LINK_NETNSID}", ns=str(ns1))
+        ip(f"link add netns {ns1} link-netnsid {LINK_NETNSID} dummy1 type dummy")
+        ip(f"link add netns {ns1} dummy2 type dummy", ns=str(ns2))
+
+        ip("link del dummy1", ns=str(ns1))
+        ip("link del dummy2", ns=str(ns1))
+
+        time.sleep(1)
+        rtnl.check_ntf()
+        ksft_true(rtnl.async_msg_queue.empty(),
+                  "Received unexpected link notification")
+
+
+def validate_link_netns(netns, ifname, link_netnsid) -> bool:
+    link_info = ip(f"-d link show dev {ifname}", ns=netns, json=True)
+    if not link_info:
+        return False
+    return link_info[0].get("link_netnsid") == link_netnsid
+
+
+def test_link_net() -> None:
+    configs = [
+        # type, common args, type args, fallback to dev_net
+        ("ipvlan", "link dummy1", "", False),
+        ("macsec", "link dummy1", "", False),
+        ("macvlan", "link dummy1", "", False),
+        ("macvtap", "link dummy1", "", False),
+        ("vlan", "link dummy1", "id 100", False),
+        ("gre", "", "local 192.0.2.1", True),
+        ("vti", "", "local 192.0.2.1", True),
+        ("ipip", "", "local 192.0.2.1", True),
+        ("ip6gre", "", "local 2001:db8::1", True),
+        ("ip6tnl", "", "local 2001:db8::1", True),
+        ("vti6", "", "local 2001:db8::1", True),
+        ("sit", "", "local 192.0.2.1", True),
+        ("xfrm", "", "if_id 1", True),
+    ]
+
+    with NetNS() as ns1, NetNS() as ns2, NetNS() as ns3:
+        net1, net2, net3 = str(ns1), str(ns2), str(ns3)
+
+        # prepare link netnsid  and a dummy link needed by certain drivers
+        ip(f"netns set {net3} {LINK_NETNSID}", ns=str(net2))
+        ip("link add dummy1 type dummy", ns=net3)
+
+        cases = [
+            # source, "netns", "link-netns", expected link-netns
+            (net3, None, None, None, None),
+            (net3, net2, None, None, LINK_NETNSID),
+            (net2, None, net3, LINK_NETNSID, LINK_NETNSID),
+            (net1, net2, net3, LINK_NETNSID, LINK_NETNSID),
+        ]
+
+        for src_net, netns, link_netns, exp1, exp2 in cases:
+            tgt_net = netns or src_net
+            for typ, cargs, targs, fb_dev_net in configs:
+                cmd = "link add"
+                if netns:
+                    cmd += f" netns {netns}"
+                if link_netns:
+                    cmd += f" link-netns {link_netns}"
+                cmd += f" {cargs} foo type {typ} {targs}"
+                ip(cmd, ns=src_net)
+                if fb_dev_net:
+                    ksft_true(validate_link_netns(tgt_net, "foo", exp1),
+                              f"{typ} link_netns validation failed")
+                else:
+                    ksft_true(validate_link_netns(tgt_net, "foo", exp2),
+                              f"{typ} link_netns validation failed")
+                ip(f"link del foo", ns=tgt_net)
+
+
+def test_peer_net() -> None:
+    types = [
+        "vxcan",
+        "netkit",
+        "veth",
+    ]
+
+    with NetNS() as ns1, NetNS() as ns2, NetNS() as ns3, NetNS() as ns4:
+        net1, net2, net3, net4 = str(ns1), str(ns2), str(ns3), str(ns4)
+
+        ip(f"netns set {net3} {LINK_NETNSID}", ns=str(net2))
+
+        cases = [
+            # source, "netns", "link-netns", "peer netns", expected
+            (net1, None, None, None, None),
+            (net1, net2, None, None, None),
+            (net2, None, net3, None, LINK_NETNSID),
+            (net1, net2, net3, None, None),
+            (net2, None, None, net3, LINK_NETNSID),
+            (net1, net2, None, net3, LINK_NETNSID),
+            (net2, None, net2, net3, LINK_NETNSID),
+            (net1, net2, net4, net3, LINK_NETNSID),
+        ]
+
+        for src_net, netns, link_netns, peer_netns, exp in cases:
+            tgt_net = netns or src_net
+            for typ in types:
+                cmd = "link add"
+                if netns:
+                    cmd += f" netns {netns}"
+                if link_netns:
+                    cmd += f" link-netns {link_netns}"
+                cmd += f" foo type {typ}"
+                if peer_netns:
+                    cmd += f" peer netns {peer_netns}"
+                ip(cmd, ns=src_net)
+                ksft_true(validate_link_netns(tgt_net, "foo", exp),
+                          f"{typ} peer_netns validation failed")
+                ip(f"link del foo", ns=tgt_net)
+
+
+def main() -> None:
+    ksft_run([test_event, test_link_net, test_peer_net])
+    ksft_exit()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/testing/selftests/net/netns-name.sh b/tools/testing/selftests/net/netns-name.sh
index 6974474c26f3..0be1905d1f2f 100755
--- a/tools/testing/selftests/net/netns-name.sh
+++ b/tools/testing/selftests/net/netns-name.sh
@@ -78,6 +78,16 @@ ip -netns $NS link show dev $ALT_NAME 2> /dev/null &&
     fail "Can still find alt-name after move"
 ip -netns $test_ns link del $DEV || fail
 
+#
+# Test no conflict of the same name/ifindex in different netns
+#
+ip -netns $NS link add name $DEV index 100 type dummy || fail
+ip -netns $NS link add netns $test_ns name $DEV index 100 type dummy ||
+    fail "Can create in netns without moving"
+ip -netns $test_ns link show dev $DEV >> /dev/null || fail "Device not found"
+ip -netns $NS link del $DEV || fail
+ip -netns $test_ns link del $DEV || fail
+
 echo -ne "$(basename $0) \t\t\t\t"
 if [ $RET_CODE -eq 0 ]; then
     echo "[  OK  ]"
-- 
2.47.1


^ permalink raw reply related

* [PATCH net-next v8 10/11] selftests: net: Add python context manager for netns entering
From: Xiao Liang @ 2025-01-13 14:37 UTC (permalink / raw)
  To: netdev, linux-kselftest, Kuniyuki Iwashima, Jakub Kicinski
  Cc: David S. Miller, David Ahern, Eric Dumazet, Paolo Abeni,
	Andrew Lunn, Simon Horman, Shuah Khan, Donald Hunter,
	Alexander Aring, Stefan Schmidt, Miquel Raynal, Steffen Klassert,
	Herbert Xu, linux-rdma, linux-can, osmocom-net-gprs, bpf,
	linux-ppp, wireguard, linux-wireless, b.a.t.m.a.n, bridge,
	linux-wpan, linux-kernel
In-Reply-To: <20250113143719.7948-1-shaw.leon@gmail.com>

Change netns of current thread and switch back on context exit.
For example:

    with NetNSEnter("ns1"):
        ip("link add dummy0 type dummy")

The command be executed in netns "ns1".

Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
---
 tools/testing/selftests/net/lib/py/__init__.py |  2 +-
 tools/testing/selftests/net/lib/py/netns.py    | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/lib/py/__init__.py b/tools/testing/selftests/net/lib/py/__init__.py
index 54d8f5eba810..e2d6c7b63019 100644
--- a/tools/testing/selftests/net/lib/py/__init__.py
+++ b/tools/testing/selftests/net/lib/py/__init__.py
@@ -2,7 +2,7 @@
 
 from .consts import KSRC
 from .ksft import *
-from .netns import NetNS
+from .netns import NetNS, NetNSEnter
 from .nsim import *
 from .utils import *
 from .ynl import NlError, YnlFamily, EthtoolFamily, NetdevFamily, RtnlFamily
diff --git a/tools/testing/selftests/net/lib/py/netns.py b/tools/testing/selftests/net/lib/py/netns.py
index ecff85f9074f..8e9317044eef 100644
--- a/tools/testing/selftests/net/lib/py/netns.py
+++ b/tools/testing/selftests/net/lib/py/netns.py
@@ -1,9 +1,12 @@
 # SPDX-License-Identifier: GPL-2.0
 
 from .utils import ip
+import ctypes
 import random
 import string
 
+libc = ctypes.cdll.LoadLibrary('libc.so.6')
+
 
 class NetNS:
     def __init__(self, name=None):
@@ -29,3 +32,18 @@ class NetNS:
 
     def __repr__(self):
         return f"NetNS({self.name})"
+
+
+class NetNSEnter:
+    def __init__(self, ns_name):
+        self.ns_path = f"/run/netns/{ns_name}"
+
+    def __enter__(self):
+        self.saved = open("/proc/thread-self/ns/net")
+        with open(self.ns_path) as ns_file:
+            libc.setns(ns_file.fileno(), 0)
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        libc.setns(self.saved.fileno(), 0)
+        self.saved.close()
-- 
2.47.1


^ permalink raw reply related

* [PATCH net-next v8 09/11] rtnetlink: Create link directly in target net namespace
From: Xiao Liang @ 2025-01-13 14:37 UTC (permalink / raw)
  To: netdev, linux-kselftest, Kuniyuki Iwashima, Jakub Kicinski
  Cc: David S. Miller, David Ahern, Eric Dumazet, Paolo Abeni,
	Andrew Lunn, Simon Horman, Shuah Khan, Donald Hunter,
	Alexander Aring, Stefan Schmidt, Miquel Raynal, Steffen Klassert,
	Herbert Xu, linux-rdma, linux-can, osmocom-net-gprs, bpf,
	linux-ppp, wireguard, linux-wireless, b.a.t.m.a.n, bridge,
	linux-wpan, linux-kernel
In-Reply-To: <20250113143719.7948-1-shaw.leon@gmail.com>

Make rtnl_newlink_create() create device in target namespace directly.
Avoid extra netns change when link netns is provided.

Device drivers has been converted to be aware of link netns, that is not
assuming device netns is and link netns is the same when ops->newlink()
is called.

Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
---
 net/core/rtnetlink.c | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index cce5bcd1f257..65f09ab616b5 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -3784,8 +3784,8 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
 		name_assign_type = NET_NAME_ENUM;
 	}
 
-	dev = rtnl_create_link(link_net ? : tgt_net, ifname,
-			       name_assign_type, ops, tb, extack);
+	dev = rtnl_create_link(tgt_net, ifname, name_assign_type, ops, tb,
+			       extack);
 	if (IS_ERR(dev)) {
 		err = PTR_ERR(dev);
 		goto out;
@@ -3805,11 +3805,6 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
 	err = rtnl_configure_link(dev, ifm, portid, nlh);
 	if (err < 0)
 		goto out_unregister;
-	if (link_net) {
-		err = dev_change_net_namespace(dev, tgt_net, ifname);
-		if (err < 0)
-			goto out_unregister;
-	}
 	if (tb[IFLA_MASTER]) {
 		err = do_set_master(dev, nla_get_u32(tb[IFLA_MASTER]), extack);
 		if (err)
-- 
2.47.1


^ permalink raw reply related

* [PATCH net-next v8 08/11] rtnetlink: Remove "net" from newlink params
From: Xiao Liang @ 2025-01-13 14:37 UTC (permalink / raw)
  To: netdev, linux-kselftest, Kuniyuki Iwashima, Jakub Kicinski
  Cc: David S. Miller, David Ahern, Eric Dumazet, Paolo Abeni,
	Andrew Lunn, Simon Horman, Shuah Khan, Donald Hunter,
	Alexander Aring, Stefan Schmidt, Miquel Raynal, Steffen Klassert,
	Herbert Xu, linux-rdma, linux-can, osmocom-net-gprs, bpf,
	linux-ppp, wireguard, linux-wireless, b.a.t.m.a.n, bridge,
	linux-wpan, linux-kernel
In-Reply-To: <20250113143719.7948-1-shaw.leon@gmail.com>

Now that devices have been converted to use the specific netns instead
of ambiguous "net", let's remove it from newlink parameters.

Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
---
 include/net/rtnetlink.h | 2 --
 net/core/rtnetlink.c    | 6 ------
 2 files changed, 8 deletions(-)

diff --git a/include/net/rtnetlink.h b/include/net/rtnetlink.h
index 00c086ca0c11..dd51240431b8 100644
--- a/include/net/rtnetlink.h
+++ b/include/net/rtnetlink.h
@@ -72,7 +72,6 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh)
 /**
  *	struct rtnl_newlink_params - parameters of rtnl_link_ops::newlink()
  *
- *	@net: Netns of interest
  *	@src_net: Source netns of rtnetlink socket
  *	@link_net: Link netns by IFLA_LINK_NETNSID, NULL if not specified
  *	@peer_net: Peer netns
@@ -80,7 +79,6 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh)
  *	@data: IFLA_INFO_DATA attributes
  */
 struct rtnl_newlink_params {
-	struct net *net;
 	struct net *src_net;
 	struct net *link_net;
 	struct net *peer_net;
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 7ff5e96f6ba7..cce5bcd1f257 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -3767,7 +3767,6 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
 	char ifname[IFNAMSIZ];
 	int err;
 	struct rtnl_newlink_params params = {
-		.net = net,
 		.src_net = net,
 		.link_net = link_net,
 		.peer_net = peer_net,
@@ -3794,11 +3793,6 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
 
 	dev->ifindex = ifm->ifi_index;
 
-	if (link_net)
-		params.net = link_net;
-	if (peer_net)
-		params.net = peer_net;
-
 	if (ops->newlink)
 		err = ops->newlink(dev, &params, extack);
 	else
-- 
2.47.1


^ permalink raw reply related

* [PATCH net-next v8 07/11] net: xfrm: Use link netns in newlink() of rtnl_link_ops
From: Xiao Liang @ 2025-01-13 14:37 UTC (permalink / raw)
  To: netdev, linux-kselftest, Kuniyuki Iwashima, Jakub Kicinski
  Cc: David S. Miller, David Ahern, Eric Dumazet, Paolo Abeni,
	Andrew Lunn, Simon Horman, Shuah Khan, Donald Hunter,
	Alexander Aring, Stefan Schmidt, Miquel Raynal, Steffen Klassert,
	Herbert Xu, linux-rdma, linux-can, osmocom-net-gprs, bpf,
	linux-ppp, wireguard, linux-wireless, b.a.t.m.a.n, bridge,
	linux-wpan, linux-kernel
In-Reply-To: <20250113143719.7948-1-shaw.leon@gmail.com>

When link_net is set, use it as link netns instead of dev_net(). This
prepares for rtnetlink core to create device in target netns directly,
in which case the two namespaces may be different.

Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
---
 net/xfrm/xfrm_interface_core.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/net/xfrm/xfrm_interface_core.c b/net/xfrm/xfrm_interface_core.c
index b7ac558025d5..b87a5c950833 100644
--- a/net/xfrm/xfrm_interface_core.c
+++ b/net/xfrm/xfrm_interface_core.c
@@ -242,10 +242,9 @@ static void xfrmi_dev_free(struct net_device *dev)
 	gro_cells_destroy(&xi->gro_cells);
 }
 
-static int xfrmi_create(struct net_device *dev)
+static int xfrmi_create(struct net *net, struct net_device *dev)
 {
 	struct xfrm_if *xi = netdev_priv(dev);
-	struct net *net = dev_net(dev);
 	struct xfrmi_net *xfrmn = net_generic(net, xfrmi_net_id);
 	int err;
 
@@ -819,11 +818,12 @@ static int xfrmi_newlink(struct net_device *dev,
 			 struct netlink_ext_ack *extack)
 {
 	struct nlattr **data = params->data;
-	struct net *net = dev_net(dev);
 	struct xfrm_if_parms p = {};
 	struct xfrm_if *xi;
+	struct net *net;
 	int err;
 
+	net = params->link_net ? : dev_net(dev);
 	xfrmi_netlink_parms(data, &p);
 	if (p.collect_md) {
 		struct xfrmi_net *xfrmn = net_generic(net, xfrmi_net_id);
@@ -852,7 +852,7 @@ static int xfrmi_newlink(struct net_device *dev,
 	xi->net = net;
 	xi->dev = dev;
 
-	err = xfrmi_create(dev);
+	err = xfrmi_create(net, dev);
 	return err;
 }
 
-- 
2.47.1


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox