chiark / gitweb /
udev: net_setup_link - open ethtool and rtnl connections lazily
[elogind.git] / src / udev / net / ethtool-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4  This file is part of systemd.
5
6  Copyright (C) 2013 Tom Gundersen <teg@jklm.no>
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/ioctl.h>
23 #include <net/if.h>
24 #include <linux/ethtool.h>
25 #include <linux/sockios.h>
26
27 #include "ethtool-util.h"
28
29 #include "strxcpyx.h"
30 #include "util.h"
31 #include "log.h"
32 #include "conf-parser.h"
33
34 static const char* const duplex_table[_DUP_MAX] = {
35         [DUP_FULL] = "full",
36         [DUP_HALF] = "half"
37 };
38
39 DEFINE_STRING_TABLE_LOOKUP(duplex, Duplex);
40 DEFINE_CONFIG_PARSE_ENUM(config_parse_duplex, duplex, Duplex, "Failed to parse duplex setting");
41
42 static const char* const wol_table[_WOL_MAX] = {
43         [WOL_PHY] = "phy",
44         [WOL_MAGIC] = "magic",
45         [WOL_OFF] = "off"
46 };
47
48 DEFINE_STRING_TABLE_LOOKUP(wol, WakeOnLan);
49 DEFINE_CONFIG_PARSE_ENUM(config_parse_wol, wol, WakeOnLan, "Failed to parse WakeOnLan setting");
50
51 int ethtool_connect(int *ret) {
52         int fd;
53
54         assert_return(ret, -EINVAL);
55
56         fd = socket(PF_INET, SOCK_DGRAM, 0);
57         if (fd < 0) {
58                 return -errno;
59         }
60
61         *ret = fd;
62
63         return 0;
64 }
65
66 int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
67         struct ethtool_drvinfo ecmd = {
68                 .cmd = ETHTOOL_GDRVINFO
69         };
70         struct ifreq ifr = {
71                 .ifr_data = (void*) &ecmd
72         };
73         char *d;
74         int r;
75
76         if (*fd < 0) {
77                 r = ethtool_connect(fd);
78                 if (r < 0) {
79                         log_warning("link_config: could not connect to ethtool: %s", strerror(-r));
80                         return r;
81                 }
82         }
83
84         strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
85
86         r = ioctl(*fd, SIOCETHTOOL, &ifr);
87         if (r < 0)
88                 return -errno;
89
90         d = strdup(ecmd.driver);
91         if (!d)
92                 return -ENOMEM;
93
94         *ret = d;
95         return 0;
96 }
97
98 int ethtool_set_speed(int *fd, const char *ifname, unsigned int speed, Duplex duplex)
99 {
100         struct ethtool_cmd ecmd = {
101                 .cmd = ETHTOOL_GSET
102         };
103         struct ifreq ifr = {
104                 .ifr_data = (void*) &ecmd
105         };
106         bool need_update = false;
107         int r;
108
109         if (speed == 0 && duplex == _DUP_INVALID)
110                 return 0;
111
112         if (*fd < 0) {
113                 r = ethtool_connect(fd);
114                 if (r < 0) {
115                         log_warning("link_config: could not connect to ethtool: %s", strerror(-r));
116                         return r;
117                 }
118         }
119
120         strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
121
122         r = ioctl(*fd, SIOCETHTOOL, &ifr);
123         if (r < 0)
124                 return -errno;
125
126         if (ethtool_cmd_speed(&ecmd) != speed) {
127                 ethtool_cmd_speed_set(&ecmd, speed);
128                 need_update = true;
129         }
130
131         switch (duplex) {
132                 case DUP_HALF:
133                         if (ecmd.duplex != DUPLEX_HALF) {
134                                 ecmd.duplex = DUPLEX_HALF;
135                                 need_update = true;
136                         }
137                         break;
138                 case DUP_FULL:
139                         if (ecmd.duplex != DUPLEX_FULL) {
140                                 ecmd.duplex = DUPLEX_FULL;
141                                 need_update = true;
142                         }
143                         break;
144                 default:
145                         break;
146         }
147
148         if (need_update) {
149                 ecmd.cmd = ETHTOOL_SSET;
150
151                 r = ioctl(*fd, SIOCETHTOOL, &ifr);
152                 if (r < 0)
153                         return -errno;
154         }
155
156         return 0;
157 }
158
159 int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
160         struct ethtool_wolinfo ecmd = {
161                 .cmd = ETHTOOL_GWOL
162         };
163         struct ifreq ifr = {
164                 .ifr_data = (void*) &ecmd
165         };
166         bool need_update = false;
167         int r;
168
169         if (wol == _WOL_INVALID)
170                 return 0;
171
172         if (*fd < 0) {
173                 r = ethtool_connect(fd);
174                 if (r < 0) {
175                         log_warning("link_config: could not connect to ethtool: %s", strerror(-r));
176                         return r;
177                 }
178         }
179
180         strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
181
182         r = ioctl(*fd, SIOCETHTOOL, &ifr);
183         if (r < 0)
184                 return -errno;
185
186         switch (wol) {
187                 case WOL_PHY:
188                         if (ecmd.wolopts != WAKE_PHY) {
189                                 ecmd.wolopts = WAKE_PHY;
190                                 need_update = true;
191                         }
192                         break;
193                 case WOL_MAGIC:
194                         if (ecmd.wolopts != WAKE_MAGIC) {
195                                 ecmd.wolopts = WAKE_MAGIC;
196                                 need_update = true;
197                         }
198                         break;
199                 case WOL_OFF:
200                         if (ecmd.wolopts != 0) {
201                                 ecmd.wolopts = 0;
202                                 need_update = true;
203                         }
204                         break;
205                 default:
206                         break;
207         }
208
209         if (need_update) {
210                 ecmd.cmd = ETHTOOL_SWOL;
211
212                 r = ioctl(*fd, SIOCETHTOOL, &ifr);
213                 if (r < 0)
214                         return -errno;
215         }
216
217         return 0;
218 }