xref: /openbmc/linux/net/dsa/dsa.c (revision bd954b826032e7bd6be8a53e30eb81c1b348aef6)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * net/dsa/dsa.c - Hardware switch handling
4  * Copyright (c) 2008-2009 Marvell Semiconductor
5  * Copyright (c) 2013 Florian Fainelli <florian@openwrt.org>
6  */
7 
8 #include <linux/device.h>
9 #include <linux/list.h>
10 #include <linux/module.h>
11 #include <linux/netdevice.h>
12 #include <linux/sysfs.h>
13 
14 #include "dsa_priv.h"
15 #include "slave.h"
16 #include "tag.h"
17 
18 static int dev_is_class(struct device *dev, void *class)
19 {
20 	if (dev->class != NULL && !strcmp(dev->class->name, class))
21 		return 1;
22 
23 	return 0;
24 }
25 
26 static struct device *dev_find_class(struct device *parent, char *class)
27 {
28 	if (dev_is_class(parent, class)) {
29 		get_device(parent);
30 		return parent;
31 	}
32 
33 	return device_find_child(parent, class, dev_is_class);
34 }
35 
36 struct net_device *dsa_dev_to_net_device(struct device *dev)
37 {
38 	struct device *d;
39 
40 	d = dev_find_class(dev, "net");
41 	if (d != NULL) {
42 		struct net_device *nd;
43 
44 		nd = to_net_dev(d);
45 		dev_hold(nd);
46 		put_device(d);
47 
48 		return nd;
49 	}
50 
51 	return NULL;
52 }
53 
54 #ifdef CONFIG_PM_SLEEP
55 static bool dsa_port_is_initialized(const struct dsa_port *dp)
56 {
57 	return dp->type == DSA_PORT_TYPE_USER && dp->slave;
58 }
59 
60 int dsa_switch_suspend(struct dsa_switch *ds)
61 {
62 	struct dsa_port *dp;
63 	int ret = 0;
64 
65 	/* Suspend slave network devices */
66 	dsa_switch_for_each_port(dp, ds) {
67 		if (!dsa_port_is_initialized(dp))
68 			continue;
69 
70 		ret = dsa_slave_suspend(dp->slave);
71 		if (ret)
72 			return ret;
73 	}
74 
75 	if (ds->ops->suspend)
76 		ret = ds->ops->suspend(ds);
77 
78 	return ret;
79 }
80 EXPORT_SYMBOL_GPL(dsa_switch_suspend);
81 
82 int dsa_switch_resume(struct dsa_switch *ds)
83 {
84 	struct dsa_port *dp;
85 	int ret = 0;
86 
87 	if (ds->ops->resume)
88 		ret = ds->ops->resume(ds);
89 
90 	if (ret)
91 		return ret;
92 
93 	/* Resume slave network devices */
94 	dsa_switch_for_each_port(dp, ds) {
95 		if (!dsa_port_is_initialized(dp))
96 			continue;
97 
98 		ret = dsa_slave_resume(dp->slave);
99 		if (ret)
100 			return ret;
101 	}
102 
103 	return 0;
104 }
105 EXPORT_SYMBOL_GPL(dsa_switch_resume);
106 #endif
107 
108 static struct workqueue_struct *dsa_owq;
109 
110 bool dsa_schedule_work(struct work_struct *work)
111 {
112 	return queue_work(dsa_owq, work);
113 }
114 
115 void dsa_flush_workqueue(void)
116 {
117 	flush_workqueue(dsa_owq);
118 }
119 EXPORT_SYMBOL_GPL(dsa_flush_workqueue);
120 
121 struct dsa_port *dsa_port_from_netdev(struct net_device *netdev)
122 {
123 	if (!netdev || !dsa_slave_dev_check(netdev))
124 		return ERR_PTR(-ENODEV);
125 
126 	return dsa_slave_to_port(netdev);
127 }
128 EXPORT_SYMBOL_GPL(dsa_port_from_netdev);
129 
130 bool dsa_db_equal(const struct dsa_db *a, const struct dsa_db *b)
131 {
132 	if (a->type != b->type)
133 		return false;
134 
135 	switch (a->type) {
136 	case DSA_DB_PORT:
137 		return a->dp == b->dp;
138 	case DSA_DB_LAG:
139 		return a->lag.dev == b->lag.dev;
140 	case DSA_DB_BRIDGE:
141 		return a->bridge.num == b->bridge.num;
142 	default:
143 		WARN_ON(1);
144 		return false;
145 	}
146 }
147 
148 bool dsa_fdb_present_in_other_db(struct dsa_switch *ds, int port,
149 				 const unsigned char *addr, u16 vid,
150 				 struct dsa_db db)
151 {
152 	struct dsa_port *dp = dsa_to_port(ds, port);
153 	struct dsa_mac_addr *a;
154 
155 	lockdep_assert_held(&dp->addr_lists_lock);
156 
157 	list_for_each_entry(a, &dp->fdbs, list) {
158 		if (!ether_addr_equal(a->addr, addr) || a->vid != vid)
159 			continue;
160 
161 		if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
162 			return true;
163 	}
164 
165 	return false;
166 }
167 EXPORT_SYMBOL_GPL(dsa_fdb_present_in_other_db);
168 
169 bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
170 				 const struct switchdev_obj_port_mdb *mdb,
171 				 struct dsa_db db)
172 {
173 	struct dsa_port *dp = dsa_to_port(ds, port);
174 	struct dsa_mac_addr *a;
175 
176 	lockdep_assert_held(&dp->addr_lists_lock);
177 
178 	list_for_each_entry(a, &dp->mdbs, list) {
179 		if (!ether_addr_equal(a->addr, mdb->addr) || a->vid != mdb->vid)
180 			continue;
181 
182 		if (a->db.type == db.type && !dsa_db_equal(&a->db, &db))
183 			return true;
184 	}
185 
186 	return false;
187 }
188 EXPORT_SYMBOL_GPL(dsa_mdb_present_in_other_db);
189 
190 static int __init dsa_init_module(void)
191 {
192 	int rc;
193 
194 	dsa_owq = alloc_ordered_workqueue("dsa_ordered",
195 					  WQ_MEM_RECLAIM);
196 	if (!dsa_owq)
197 		return -ENOMEM;
198 
199 	rc = dsa_slave_register_notifier();
200 	if (rc)
201 		goto register_notifier_fail;
202 
203 	dev_add_pack(&dsa_pack_type);
204 
205 	rc = rtnl_link_register(&dsa_link_ops);
206 	if (rc)
207 		goto netlink_register_fail;
208 
209 	return 0;
210 
211 netlink_register_fail:
212 	dsa_slave_unregister_notifier();
213 	dev_remove_pack(&dsa_pack_type);
214 register_notifier_fail:
215 	destroy_workqueue(dsa_owq);
216 
217 	return rc;
218 }
219 module_init(dsa_init_module);
220 
221 static void __exit dsa_cleanup_module(void)
222 {
223 	rtnl_link_unregister(&dsa_link_ops);
224 
225 	dsa_slave_unregister_notifier();
226 	dev_remove_pack(&dsa_pack_type);
227 	destroy_workqueue(dsa_owq);
228 }
229 module_exit(dsa_cleanup_module);
230 
231 MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
232 MODULE_DESCRIPTION("Driver for Distributed Switch Architecture switch chips");
233 MODULE_LICENSE("GPL");
234 MODULE_ALIAS("platform:dsa");
235