18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Generic HDLC support routines for Linux
48c2ecf20Sopenharmony_ci * HDLC Ethernet emulation support
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 2002-2006 Krzysztof Halasa <khc@pm.waw.pl>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/errno.h>
108c2ecf20Sopenharmony_ci#include <linux/etherdevice.h>
118c2ecf20Sopenharmony_ci#include <linux/gfp.h>
128c2ecf20Sopenharmony_ci#include <linux/hdlc.h>
138c2ecf20Sopenharmony_ci#include <linux/if_arp.h>
148c2ecf20Sopenharmony_ci#include <linux/inetdevice.h>
158c2ecf20Sopenharmony_ci#include <linux/init.h>
168c2ecf20Sopenharmony_ci#include <linux/kernel.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/pkt_sched.h>
198c2ecf20Sopenharmony_ci#include <linux/poll.h>
208c2ecf20Sopenharmony_ci#include <linux/rtnetlink.h>
218c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic int raw_eth_ioctl(struct net_device *dev, struct ifreq *ifr);
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic netdev_tx_t eth_tx(struct sk_buff *skb, struct net_device *dev)
268c2ecf20Sopenharmony_ci{
278c2ecf20Sopenharmony_ci	int pad = ETH_ZLEN - skb->len;
288c2ecf20Sopenharmony_ci	if (pad > 0) {		/* Pad the frame with zeros */
298c2ecf20Sopenharmony_ci		int len = skb->len;
308c2ecf20Sopenharmony_ci		if (skb_tailroom(skb) < pad)
318c2ecf20Sopenharmony_ci			if (pskb_expand_head(skb, 0, pad, GFP_ATOMIC)) {
328c2ecf20Sopenharmony_ci				dev->stats.tx_dropped++;
338c2ecf20Sopenharmony_ci				dev_kfree_skb(skb);
348c2ecf20Sopenharmony_ci				return 0;
358c2ecf20Sopenharmony_ci			}
368c2ecf20Sopenharmony_ci		skb_put(skb, pad);
378c2ecf20Sopenharmony_ci		memset(skb->data + len, 0, pad);
388c2ecf20Sopenharmony_ci	}
398c2ecf20Sopenharmony_ci	return dev_to_hdlc(dev)->xmit(skb, dev);
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic struct hdlc_proto proto = {
448c2ecf20Sopenharmony_ci	.type_trans	= eth_type_trans,
458c2ecf20Sopenharmony_ci	.xmit		= eth_tx,
468c2ecf20Sopenharmony_ci	.ioctl		= raw_eth_ioctl,
478c2ecf20Sopenharmony_ci	.module		= THIS_MODULE,
488c2ecf20Sopenharmony_ci};
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_cistatic int raw_eth_ioctl(struct net_device *dev, struct ifreq *ifr)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	raw_hdlc_proto __user *raw_s = ifr->ifr_settings.ifs_ifsu.raw_hdlc;
548c2ecf20Sopenharmony_ci	const size_t size = sizeof(raw_hdlc_proto);
558c2ecf20Sopenharmony_ci	raw_hdlc_proto new_settings;
568c2ecf20Sopenharmony_ci	hdlc_device *hdlc = dev_to_hdlc(dev);
578c2ecf20Sopenharmony_ci	unsigned int old_qlen;
588c2ecf20Sopenharmony_ci	int result;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	switch (ifr->ifr_settings.type) {
618c2ecf20Sopenharmony_ci	case IF_GET_PROTO:
628c2ecf20Sopenharmony_ci		if (dev_to_hdlc(dev)->proto != &proto)
638c2ecf20Sopenharmony_ci			return -EINVAL;
648c2ecf20Sopenharmony_ci		ifr->ifr_settings.type = IF_PROTO_HDLC_ETH;
658c2ecf20Sopenharmony_ci		if (ifr->ifr_settings.size < size) {
668c2ecf20Sopenharmony_ci			ifr->ifr_settings.size = size; /* data size wanted */
678c2ecf20Sopenharmony_ci			return -ENOBUFS;
688c2ecf20Sopenharmony_ci		}
698c2ecf20Sopenharmony_ci		if (copy_to_user(raw_s, hdlc->state, size))
708c2ecf20Sopenharmony_ci			return -EFAULT;
718c2ecf20Sopenharmony_ci		return 0;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	case IF_PROTO_HDLC_ETH:
748c2ecf20Sopenharmony_ci		if (!capable(CAP_NET_ADMIN))
758c2ecf20Sopenharmony_ci			return -EPERM;
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci		if (dev->flags & IFF_UP)
788c2ecf20Sopenharmony_ci			return -EBUSY;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci		if (copy_from_user(&new_settings, raw_s, size))
818c2ecf20Sopenharmony_ci			return -EFAULT;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci		if (new_settings.encoding == ENCODING_DEFAULT)
848c2ecf20Sopenharmony_ci			new_settings.encoding = ENCODING_NRZ;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci		if (new_settings.parity == PARITY_DEFAULT)
878c2ecf20Sopenharmony_ci			new_settings.parity = PARITY_CRC16_PR1_CCITT;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci		result = hdlc->attach(dev, new_settings.encoding,
908c2ecf20Sopenharmony_ci				      new_settings.parity);
918c2ecf20Sopenharmony_ci		if (result)
928c2ecf20Sopenharmony_ci			return result;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci		result = attach_hdlc_protocol(dev, &proto,
958c2ecf20Sopenharmony_ci					      sizeof(raw_hdlc_proto));
968c2ecf20Sopenharmony_ci		if (result)
978c2ecf20Sopenharmony_ci			return result;
988c2ecf20Sopenharmony_ci		memcpy(hdlc->state, &new_settings, size);
998c2ecf20Sopenharmony_ci		old_qlen = dev->tx_queue_len;
1008c2ecf20Sopenharmony_ci		ether_setup(dev);
1018c2ecf20Sopenharmony_ci		dev->tx_queue_len = old_qlen;
1028c2ecf20Sopenharmony_ci		dev->priv_flags &= ~IFF_TX_SKB_SHARING;
1038c2ecf20Sopenharmony_ci		eth_hw_addr_random(dev);
1048c2ecf20Sopenharmony_ci		call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev);
1058c2ecf20Sopenharmony_ci		netif_dormant_off(dev);
1068c2ecf20Sopenharmony_ci		return 0;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return -EINVAL;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic int __init mod_init(void)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	register_hdlc_protocol(&proto);
1168c2ecf20Sopenharmony_ci	return 0;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_cistatic void __exit mod_exit(void)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	unregister_hdlc_protocol(&proto);
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_cimodule_init(mod_init);
1288c2ecf20Sopenharmony_cimodule_exit(mod_exit);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ciMODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>");
1318c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Ethernet encapsulation support for generic HDLC");
1328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
133