162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Generic HDLC support routines for Linux 462306a36Sopenharmony_ci * Cisco HDLC support 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2000 - 2006 Krzysztof Halasa <khc@pm.waw.pl> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/errno.h> 1062306a36Sopenharmony_ci#include <linux/hdlc.h> 1162306a36Sopenharmony_ci#include <linux/if_arp.h> 1262306a36Sopenharmony_ci#include <linux/inetdevice.h> 1362306a36Sopenharmony_ci#include <linux/init.h> 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/pkt_sched.h> 1762306a36Sopenharmony_ci#include <linux/poll.h> 1862306a36Sopenharmony_ci#include <linux/rtnetlink.h> 1962306a36Sopenharmony_ci#include <linux/skbuff.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#undef DEBUG_HARD_HEADER 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define CISCO_MULTICAST 0x8F /* Cisco multicast address */ 2462306a36Sopenharmony_ci#define CISCO_UNICAST 0x0F /* Cisco unicast address */ 2562306a36Sopenharmony_ci#define CISCO_KEEPALIVE 0x8035 /* Cisco keepalive protocol */ 2662306a36Sopenharmony_ci#define CISCO_SYS_INFO 0x2000 /* Cisco interface/system info */ 2762306a36Sopenharmony_ci#define CISCO_ADDR_REQ 0 /* Cisco address request */ 2862306a36Sopenharmony_ci#define CISCO_ADDR_REPLY 1 /* Cisco address reply */ 2962306a36Sopenharmony_ci#define CISCO_KEEPALIVE_REQ 2 /* Cisco keepalive request */ 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistruct hdlc_header { 3262306a36Sopenharmony_ci u8 address; 3362306a36Sopenharmony_ci u8 control; 3462306a36Sopenharmony_ci __be16 protocol; 3562306a36Sopenharmony_ci} __packed; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistruct cisco_packet { 3862306a36Sopenharmony_ci __be32 type; /* code */ 3962306a36Sopenharmony_ci __be32 par1; 4062306a36Sopenharmony_ci __be32 par2; 4162306a36Sopenharmony_ci __be16 rel; /* reliability */ 4262306a36Sopenharmony_ci __be32 time; 4362306a36Sopenharmony_ci} __packed; 4462306a36Sopenharmony_ci#define CISCO_PACKET_LEN 18 4562306a36Sopenharmony_ci#define CISCO_BIG_PACKET_LEN 20 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistruct cisco_state { 4862306a36Sopenharmony_ci cisco_proto settings; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci struct timer_list timer; 5162306a36Sopenharmony_ci struct net_device *dev; 5262306a36Sopenharmony_ci spinlock_t lock; 5362306a36Sopenharmony_ci unsigned long last_poll; 5462306a36Sopenharmony_ci int up; 5562306a36Sopenharmony_ci u32 txseq; /* TX sequence number, 0 = none */ 5662306a36Sopenharmony_ci u32 rxseq; /* RX sequence number */ 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic int cisco_ioctl(struct net_device *dev, struct if_settings *ifs); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic inline struct cisco_state *state(hdlc_device *hdlc) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci return (struct cisco_state *)hdlc->state; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic int cisco_hard_header(struct sk_buff *skb, struct net_device *dev, 6762306a36Sopenharmony_ci u16 type, const void *daddr, const void *saddr, 6862306a36Sopenharmony_ci unsigned int len) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci struct hdlc_header *data; 7162306a36Sopenharmony_ci#ifdef DEBUG_HARD_HEADER 7262306a36Sopenharmony_ci netdev_dbg(dev, "%s called\n", __func__); 7362306a36Sopenharmony_ci#endif 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci skb_push(skb, sizeof(struct hdlc_header)); 7662306a36Sopenharmony_ci data = (struct hdlc_header *)skb->data; 7762306a36Sopenharmony_ci if (type == CISCO_KEEPALIVE) 7862306a36Sopenharmony_ci data->address = CISCO_MULTICAST; 7962306a36Sopenharmony_ci else 8062306a36Sopenharmony_ci data->address = CISCO_UNICAST; 8162306a36Sopenharmony_ci data->control = 0; 8262306a36Sopenharmony_ci data->protocol = htons(type); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ci return sizeof(struct hdlc_header); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic void cisco_keepalive_send(struct net_device *dev, u32 type, 8862306a36Sopenharmony_ci __be32 par1, __be32 par2) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct sk_buff *skb; 9162306a36Sopenharmony_ci struct cisco_packet *data; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci skb = dev_alloc_skb(sizeof(struct hdlc_header) + 9462306a36Sopenharmony_ci sizeof(struct cisco_packet)); 9562306a36Sopenharmony_ci if (!skb) 9662306a36Sopenharmony_ci return; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci skb_reserve(skb, 4); 9962306a36Sopenharmony_ci cisco_hard_header(skb, dev, CISCO_KEEPALIVE, NULL, NULL, 0); 10062306a36Sopenharmony_ci data = (struct cisco_packet *)(skb->data + 4); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci data->type = htonl(type); 10362306a36Sopenharmony_ci data->par1 = par1; 10462306a36Sopenharmony_ci data->par2 = par2; 10562306a36Sopenharmony_ci data->rel = cpu_to_be16(0xFFFF); 10662306a36Sopenharmony_ci /* we will need do_div here if 1000 % HZ != 0 */ 10762306a36Sopenharmony_ci data->time = htonl((jiffies - INITIAL_JIFFIES) * (1000 / HZ)); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci skb_put(skb, sizeof(struct cisco_packet)); 11062306a36Sopenharmony_ci skb->priority = TC_PRIO_CONTROL; 11162306a36Sopenharmony_ci skb->dev = dev; 11262306a36Sopenharmony_ci skb->protocol = htons(ETH_P_HDLC); 11362306a36Sopenharmony_ci skb_reset_network_header(skb); 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci dev_queue_xmit(skb); 11662306a36Sopenharmony_ci} 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_cistatic __be16 cisco_type_trans(struct sk_buff *skb, struct net_device *dev) 11962306a36Sopenharmony_ci{ 12062306a36Sopenharmony_ci struct hdlc_header *data = (struct hdlc_header *)skb->data; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci if (skb->len < sizeof(struct hdlc_header)) 12362306a36Sopenharmony_ci return cpu_to_be16(ETH_P_HDLC); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (data->address != CISCO_MULTICAST && 12662306a36Sopenharmony_ci data->address != CISCO_UNICAST) 12762306a36Sopenharmony_ci return cpu_to_be16(ETH_P_HDLC); 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci switch (data->protocol) { 13062306a36Sopenharmony_ci case cpu_to_be16(ETH_P_IP): 13162306a36Sopenharmony_ci case cpu_to_be16(ETH_P_IPX): 13262306a36Sopenharmony_ci case cpu_to_be16(ETH_P_IPV6): 13362306a36Sopenharmony_ci skb_pull(skb, sizeof(struct hdlc_header)); 13462306a36Sopenharmony_ci return data->protocol; 13562306a36Sopenharmony_ci default: 13662306a36Sopenharmony_ci return cpu_to_be16(ETH_P_HDLC); 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic int cisco_rx(struct sk_buff *skb) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci struct net_device *dev = skb->dev; 14362306a36Sopenharmony_ci hdlc_device *hdlc = dev_to_hdlc(dev); 14462306a36Sopenharmony_ci struct cisco_state *st = state(hdlc); 14562306a36Sopenharmony_ci struct hdlc_header *data = (struct hdlc_header *)skb->data; 14662306a36Sopenharmony_ci struct cisco_packet *cisco_data; 14762306a36Sopenharmony_ci struct in_device *in_dev; 14862306a36Sopenharmony_ci __be32 addr, mask; 14962306a36Sopenharmony_ci u32 ack; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (skb->len < sizeof(struct hdlc_header)) 15262306a36Sopenharmony_ci goto rx_error; 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci if (data->address != CISCO_MULTICAST && 15562306a36Sopenharmony_ci data->address != CISCO_UNICAST) 15662306a36Sopenharmony_ci goto rx_error; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci switch (ntohs(data->protocol)) { 15962306a36Sopenharmony_ci case CISCO_SYS_INFO: 16062306a36Sopenharmony_ci /* Packet is not needed, drop it. */ 16162306a36Sopenharmony_ci dev_kfree_skb_any(skb); 16262306a36Sopenharmony_ci return NET_RX_SUCCESS; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci case CISCO_KEEPALIVE: 16562306a36Sopenharmony_ci if ((skb->len != sizeof(struct hdlc_header) + 16662306a36Sopenharmony_ci CISCO_PACKET_LEN) && 16762306a36Sopenharmony_ci (skb->len != sizeof(struct hdlc_header) + 16862306a36Sopenharmony_ci CISCO_BIG_PACKET_LEN)) { 16962306a36Sopenharmony_ci netdev_info(dev, "Invalid length of Cisco control packet (%d bytes)\n", 17062306a36Sopenharmony_ci skb->len); 17162306a36Sopenharmony_ci goto rx_error; 17262306a36Sopenharmony_ci } 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci cisco_data = (struct cisco_packet *)(skb->data + sizeof 17562306a36Sopenharmony_ci (struct hdlc_header)); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci switch (ntohl(cisco_data->type)) { 17862306a36Sopenharmony_ci case CISCO_ADDR_REQ: /* Stolen from syncppp.c :-) */ 17962306a36Sopenharmony_ci rcu_read_lock(); 18062306a36Sopenharmony_ci in_dev = __in_dev_get_rcu(dev); 18162306a36Sopenharmony_ci addr = 0; 18262306a36Sopenharmony_ci mask = ~cpu_to_be32(0); /* is the mask correct? */ 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci if (in_dev != NULL) { 18562306a36Sopenharmony_ci const struct in_ifaddr *ifa; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci in_dev_for_each_ifa_rcu(ifa, in_dev) { 18862306a36Sopenharmony_ci if (strcmp(dev->name, 18962306a36Sopenharmony_ci ifa->ifa_label) == 0) { 19062306a36Sopenharmony_ci addr = ifa->ifa_local; 19162306a36Sopenharmony_ci mask = ifa->ifa_mask; 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci cisco_keepalive_send(dev, CISCO_ADDR_REPLY, 19762306a36Sopenharmony_ci addr, mask); 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci rcu_read_unlock(); 20062306a36Sopenharmony_ci dev_kfree_skb_any(skb); 20162306a36Sopenharmony_ci return NET_RX_SUCCESS; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci case CISCO_ADDR_REPLY: 20462306a36Sopenharmony_ci netdev_info(dev, "Unexpected Cisco IP address reply\n"); 20562306a36Sopenharmony_ci goto rx_error; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci case CISCO_KEEPALIVE_REQ: 20862306a36Sopenharmony_ci spin_lock(&st->lock); 20962306a36Sopenharmony_ci st->rxseq = ntohl(cisco_data->par1); 21062306a36Sopenharmony_ci ack = ntohl(cisco_data->par2); 21162306a36Sopenharmony_ci if (ack && (ack == st->txseq || 21262306a36Sopenharmony_ci /* our current REQ may be in transit */ 21362306a36Sopenharmony_ci ack == st->txseq - 1)) { 21462306a36Sopenharmony_ci st->last_poll = jiffies; 21562306a36Sopenharmony_ci if (!st->up) { 21662306a36Sopenharmony_ci u32 sec, min, hrs, days; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci sec = ntohl(cisco_data->time) / 1000; 21962306a36Sopenharmony_ci min = sec / 60; sec -= min * 60; 22062306a36Sopenharmony_ci hrs = min / 60; min -= hrs * 60; 22162306a36Sopenharmony_ci days = hrs / 24; hrs -= days * 24; 22262306a36Sopenharmony_ci netdev_info(dev, "Link up (peer uptime %ud%uh%um%us)\n", 22362306a36Sopenharmony_ci days, hrs, min, sec); 22462306a36Sopenharmony_ci netif_dormant_off(dev); 22562306a36Sopenharmony_ci st->up = 1; 22662306a36Sopenharmony_ci } 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci spin_unlock(&st->lock); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci dev_kfree_skb_any(skb); 23162306a36Sopenharmony_ci return NET_RX_SUCCESS; 23262306a36Sopenharmony_ci } /* switch (keepalive type) */ 23362306a36Sopenharmony_ci } /* switch (protocol) */ 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci netdev_info(dev, "Unsupported protocol %x\n", ntohs(data->protocol)); 23662306a36Sopenharmony_ci dev_kfree_skb_any(skb); 23762306a36Sopenharmony_ci return NET_RX_DROP; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_cirx_error: 24062306a36Sopenharmony_ci dev->stats.rx_errors++; /* Mark error */ 24162306a36Sopenharmony_ci dev_kfree_skb_any(skb); 24262306a36Sopenharmony_ci return NET_RX_DROP; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic void cisco_timer(struct timer_list *t) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci struct cisco_state *st = from_timer(st, t, timer); 24862306a36Sopenharmony_ci struct net_device *dev = st->dev; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci spin_lock(&st->lock); 25162306a36Sopenharmony_ci if (st->up && 25262306a36Sopenharmony_ci time_after(jiffies, st->last_poll + st->settings.timeout * HZ)) { 25362306a36Sopenharmony_ci st->up = 0; 25462306a36Sopenharmony_ci netdev_info(dev, "Link down\n"); 25562306a36Sopenharmony_ci netif_dormant_on(dev); 25662306a36Sopenharmony_ci } 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ, htonl(++st->txseq), 25962306a36Sopenharmony_ci htonl(st->rxseq)); 26062306a36Sopenharmony_ci spin_unlock(&st->lock); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci st->timer.expires = jiffies + st->settings.interval * HZ; 26362306a36Sopenharmony_ci add_timer(&st->timer); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic void cisco_start(struct net_device *dev) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci hdlc_device *hdlc = dev_to_hdlc(dev); 26962306a36Sopenharmony_ci struct cisco_state *st = state(hdlc); 27062306a36Sopenharmony_ci unsigned long flags; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci spin_lock_irqsave(&st->lock, flags); 27362306a36Sopenharmony_ci st->up = st->txseq = st->rxseq = 0; 27462306a36Sopenharmony_ci spin_unlock_irqrestore(&st->lock, flags); 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci st->dev = dev; 27762306a36Sopenharmony_ci timer_setup(&st->timer, cisco_timer, 0); 27862306a36Sopenharmony_ci st->timer.expires = jiffies + HZ; /* First poll after 1 s */ 27962306a36Sopenharmony_ci add_timer(&st->timer); 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic void cisco_stop(struct net_device *dev) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci hdlc_device *hdlc = dev_to_hdlc(dev); 28562306a36Sopenharmony_ci struct cisco_state *st = state(hdlc); 28662306a36Sopenharmony_ci unsigned long flags; 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci del_timer_sync(&st->timer); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci spin_lock_irqsave(&st->lock, flags); 29162306a36Sopenharmony_ci netif_dormant_on(dev); 29262306a36Sopenharmony_ci st->up = st->txseq = 0; 29362306a36Sopenharmony_ci spin_unlock_irqrestore(&st->lock, flags); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic struct hdlc_proto proto = { 29762306a36Sopenharmony_ci .start = cisco_start, 29862306a36Sopenharmony_ci .stop = cisco_stop, 29962306a36Sopenharmony_ci .type_trans = cisco_type_trans, 30062306a36Sopenharmony_ci .ioctl = cisco_ioctl, 30162306a36Sopenharmony_ci .netif_rx = cisco_rx, 30262306a36Sopenharmony_ci .module = THIS_MODULE, 30362306a36Sopenharmony_ci}; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_cistatic const struct header_ops cisco_header_ops = { 30662306a36Sopenharmony_ci .create = cisco_hard_header, 30762306a36Sopenharmony_ci}; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_cistatic int cisco_ioctl(struct net_device *dev, struct if_settings *ifs) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci cisco_proto __user *cisco_s = ifs->ifs_ifsu.cisco; 31262306a36Sopenharmony_ci const size_t size = sizeof(cisco_proto); 31362306a36Sopenharmony_ci cisco_proto new_settings; 31462306a36Sopenharmony_ci hdlc_device *hdlc = dev_to_hdlc(dev); 31562306a36Sopenharmony_ci int result; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci switch (ifs->type) { 31862306a36Sopenharmony_ci case IF_GET_PROTO: 31962306a36Sopenharmony_ci if (dev_to_hdlc(dev)->proto != &proto) 32062306a36Sopenharmony_ci return -EINVAL; 32162306a36Sopenharmony_ci ifs->type = IF_PROTO_CISCO; 32262306a36Sopenharmony_ci if (ifs->size < size) { 32362306a36Sopenharmony_ci ifs->size = size; /* data size wanted */ 32462306a36Sopenharmony_ci return -ENOBUFS; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci if (copy_to_user(cisco_s, &state(hdlc)->settings, size)) 32762306a36Sopenharmony_ci return -EFAULT; 32862306a36Sopenharmony_ci return 0; 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci case IF_PROTO_CISCO: 33162306a36Sopenharmony_ci if (!capable(CAP_NET_ADMIN)) 33262306a36Sopenharmony_ci return -EPERM; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci if (dev->flags & IFF_UP) 33562306a36Sopenharmony_ci return -EBUSY; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci if (copy_from_user(&new_settings, cisco_s, size)) 33862306a36Sopenharmony_ci return -EFAULT; 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci if (new_settings.interval < 1 || 34162306a36Sopenharmony_ci new_settings.timeout < 2) 34262306a36Sopenharmony_ci return -EINVAL; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci result = hdlc->attach(dev, ENCODING_NRZ, 34562306a36Sopenharmony_ci PARITY_CRC16_PR1_CCITT); 34662306a36Sopenharmony_ci if (result) 34762306a36Sopenharmony_ci return result; 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci result = attach_hdlc_protocol(dev, &proto, 35062306a36Sopenharmony_ci sizeof(struct cisco_state)); 35162306a36Sopenharmony_ci if (result) 35262306a36Sopenharmony_ci return result; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci memcpy(&state(hdlc)->settings, &new_settings, size); 35562306a36Sopenharmony_ci spin_lock_init(&state(hdlc)->lock); 35662306a36Sopenharmony_ci dev->header_ops = &cisco_header_ops; 35762306a36Sopenharmony_ci dev->hard_header_len = sizeof(struct hdlc_header); 35862306a36Sopenharmony_ci dev->type = ARPHRD_CISCO; 35962306a36Sopenharmony_ci call_netdevice_notifiers(NETDEV_POST_TYPE_CHANGE, dev); 36062306a36Sopenharmony_ci netif_dormant_on(dev); 36162306a36Sopenharmony_ci return 0; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci return -EINVAL; 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic int __init hdlc_cisco_init(void) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci register_hdlc_protocol(&proto); 37062306a36Sopenharmony_ci return 0; 37162306a36Sopenharmony_ci} 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_cistatic void __exit hdlc_cisco_exit(void) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci unregister_hdlc_protocol(&proto); 37662306a36Sopenharmony_ci} 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_cimodule_init(hdlc_cisco_init); 37962306a36Sopenharmony_cimodule_exit(hdlc_cisco_exit); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ciMODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); 38262306a36Sopenharmony_ciMODULE_DESCRIPTION("Cisco HDLC protocol support for generic HDLC"); 38362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 384