[WAN]: converting generic HDLC to use netif_dormant*()
This patch converts generic HDLC (and WAN drivers using it) from hdlc_set_carrier() to netif_dormant*() interface. WAN hardware drivers should now use netif_carrier_on|off() like other network drivers. Signed-off-by: Krzysztof Halasa <khc@pm.waw.pl> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Родитель
b47b2ec198
Коммит
c2ce920468
|
@ -116,27 +116,33 @@ static inline void openwin(card_t *card, u8 page)
|
||||||
#include "hd6457x.c"
|
#include "hd6457x.c"
|
||||||
|
|
||||||
|
|
||||||
|
static inline void set_carrier(port_t *port)
|
||||||
|
{
|
||||||
|
if (!sca_in(MSCI1_OFFSET + ST3, port) & ST3_DCD)
|
||||||
|
netif_carrier_on(port_to_dev(port));
|
||||||
|
else
|
||||||
|
netif_carrier_off(port_to_dev(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void sca_msci_intr(port_t *port)
|
static void sca_msci_intr(port_t *port)
|
||||||
{
|
{
|
||||||
struct net_device *dev = port_to_dev(port);
|
u8 stat = sca_in(MSCI1_OFFSET + ST1, port); /* read MSCI ST1 status */
|
||||||
card_t* card = port_to_card(port);
|
|
||||||
u8 stat = sca_in(MSCI1_OFFSET + ST1, card); /* read MSCI ST1 status */
|
|
||||||
|
|
||||||
/* Reset MSCI TX underrun status bit */
|
/* Reset MSCI TX underrun status bit */
|
||||||
sca_out(stat & ST1_UDRN, MSCI0_OFFSET + ST1, card);
|
sca_out(stat & ST1_UDRN, MSCI0_OFFSET + ST1, port);
|
||||||
|
|
||||||
if (stat & ST1_UDRN) {
|
if (stat & ST1_UDRN) {
|
||||||
struct net_device_stats *stats = hdlc_stats(dev);
|
struct net_device_stats *stats = hdlc_stats(port_to_dev(port));
|
||||||
stats->tx_errors++; /* TX Underrun error detected */
|
stats->tx_errors++; /* TX Underrun error detected */
|
||||||
stats->tx_fifo_errors++;
|
stats->tx_fifo_errors++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset MSCI CDCD status bit - uses ch#2 DCD input */
|
/* Reset MSCI CDCD status bit - uses ch#2 DCD input */
|
||||||
sca_out(stat & ST1_CDCD, MSCI1_OFFSET + ST1, card);
|
sca_out(stat & ST1_CDCD, MSCI1_OFFSET + ST1, port);
|
||||||
|
|
||||||
if (stat & ST1_CDCD)
|
if (stat & ST1_CDCD)
|
||||||
hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, card) & ST3_DCD),
|
set_carrier(port);
|
||||||
dev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,7 +196,7 @@ static int c101_open(struct net_device *dev)
|
||||||
sca_out(IE1_UDRN, MSCI0_OFFSET + IE1, port);
|
sca_out(IE1_UDRN, MSCI0_OFFSET + IE1, port);
|
||||||
sca_out(IE0_TXINT, MSCI0_OFFSET + IE0, port);
|
sca_out(IE0_TXINT, MSCI0_OFFSET + IE0, port);
|
||||||
|
|
||||||
hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, port) & ST3_DCD), dev);
|
set_carrier(port);
|
||||||
printk(KERN_DEBUG "0x%X\n", sca_in(MSCI1_OFFSET + ST3, port));
|
printk(KERN_DEBUG "0x%X\n", sca_in(MSCI1_OFFSET + ST3, port));
|
||||||
|
|
||||||
/* enable MSCI1 CDCD interrupt */
|
/* enable MSCI1 CDCD interrupt */
|
||||||
|
@ -378,7 +384,7 @@ static int __init c101_run(unsigned long irq, unsigned long winbase)
|
||||||
}
|
}
|
||||||
|
|
||||||
sca_init_sync_port(card); /* Set up C101 memory */
|
sca_init_sync_port(card); /* Set up C101 memory */
|
||||||
hdlc_set_carrier(!(sca_in(MSCI1_OFFSET + ST3, card) & ST3_DCD), dev);
|
set_carrier(card);
|
||||||
|
|
||||||
printk(KERN_INFO "%s: Moxa C101 on IRQ%u,"
|
printk(KERN_INFO "%s: Moxa C101 on IRQ%u,"
|
||||||
" using %u TX + %u RX packets rings\n",
|
" using %u TX + %u RX packets rings\n",
|
||||||
|
|
|
@ -168,6 +168,23 @@ static inline u32 buffer_offset(port_t *port, u16 desc, int transmit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline void sca_set_carrier(port_t *port)
|
||||||
|
{
|
||||||
|
if (!(sca_in(get_msci(port) + ST3, port_to_card(port)) & ST3_DCD)) {
|
||||||
|
#ifdef DEBUG_LINK
|
||||||
|
printk(KERN_DEBUG "%s: sca_set_carrier on\n",
|
||||||
|
port_to_dev(port)->name);
|
||||||
|
#endif
|
||||||
|
netif_carrier_on(port_to_dev(port));
|
||||||
|
} else {
|
||||||
|
#ifdef DEBUG_LINK
|
||||||
|
printk(KERN_DEBUG "%s: sca_set_carrier off\n",
|
||||||
|
port_to_dev(port)->name);
|
||||||
|
#endif
|
||||||
|
netif_carrier_off(port_to_dev(port));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void sca_init_sync_port(port_t *port)
|
static void sca_init_sync_port(port_t *port)
|
||||||
{
|
{
|
||||||
|
@ -237,9 +254,7 @@ static void sca_init_sync_port(port_t *port)
|
||||||
sca_out(DIR_BOFE, DIR_TX(phy_node(port)), card);
|
sca_out(DIR_BOFE, DIR_TX(phy_node(port)), card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sca_set_carrier(port);
|
||||||
hdlc_set_carrier(!(sca_in(get_msci(port) + ST3, card) & ST3_DCD),
|
|
||||||
port_to_dev(port));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,8 +277,7 @@ static inline void sca_msci_intr(port_t *port)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stat & ST1_CDCD)
|
if (stat & ST1_CDCD)
|
||||||
hdlc_set_carrier(!(sca_in(msci + ST3, card) & ST3_DCD),
|
sca_set_carrier(port);
|
||||||
port_to_dev(port));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -566,7 +580,7 @@ static void sca_open(struct net_device *dev)
|
||||||
- all DMA interrupts
|
- all DMA interrupts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
hdlc_set_carrier(!(sca_in(msci + ST3, card) & ST3_DCD), dev);
|
sca_set_carrier(port);
|
||||||
|
|
||||||
#ifdef __HD64570_H
|
#ifdef __HD64570_H
|
||||||
/* MSCI TX INT and RX INT A IRQ enable */
|
/* MSCI TX INT and RX INT A IRQ enable */
|
||||||
|
|
|
@ -192,9 +192,7 @@ static int cisco_rx(struct sk_buff *skb)
|
||||||
"uptime %ud%uh%um%us)\n",
|
"uptime %ud%uh%um%us)\n",
|
||||||
dev->name, days, hrs,
|
dev->name, days, hrs,
|
||||||
min, sec);
|
min, sec);
|
||||||
#if 0
|
netif_dormant_off(dev);
|
||||||
netif_carrier_on(dev);
|
|
||||||
#endif
|
|
||||||
hdlc->state.cisco.up = 1;
|
hdlc->state.cisco.up = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -227,9 +225,7 @@ static void cisco_timer(unsigned long arg)
|
||||||
hdlc->state.cisco.settings.timeout * HZ)) {
|
hdlc->state.cisco.settings.timeout * HZ)) {
|
||||||
hdlc->state.cisco.up = 0;
|
hdlc->state.cisco.up = 0;
|
||||||
printk(KERN_INFO "%s: Link down\n", dev->name);
|
printk(KERN_INFO "%s: Link down\n", dev->name);
|
||||||
#if 0
|
netif_dormant_on(dev);
|
||||||
netif_carrier_off(dev);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ,
|
cisco_keepalive_send(dev, CISCO_KEEPALIVE_REQ,
|
||||||
|
@ -265,10 +261,7 @@ static void cisco_stop(struct net_device *dev)
|
||||||
{
|
{
|
||||||
hdlc_device *hdlc = dev_to_hdlc(dev);
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
||||||
del_timer_sync(&hdlc->state.cisco.timer);
|
del_timer_sync(&hdlc->state.cisco.timer);
|
||||||
#if 0
|
netif_dormant_on(dev);
|
||||||
if (netif_carrier_ok(dev))
|
|
||||||
netif_carrier_off(dev);
|
|
||||||
#endif
|
|
||||||
hdlc->state.cisco.up = 0;
|
hdlc->state.cisco.up = 0;
|
||||||
hdlc->state.cisco.request_sent = 0;
|
hdlc->state.cisco.request_sent = 0;
|
||||||
}
|
}
|
||||||
|
@ -328,6 +321,7 @@ int hdlc_cisco_ioctl(struct net_device *dev, struct ifreq *ifr)
|
||||||
dev->type = ARPHRD_CISCO;
|
dev->type = ARPHRD_CISCO;
|
||||||
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
|
||||||
dev->addr_len = 0;
|
dev->addr_len = 0;
|
||||||
|
netif_dormant_on(dev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ static int pvc_open(struct net_device *dev)
|
||||||
if (pvc->open_count++ == 0) {
|
if (pvc->open_count++ == 0) {
|
||||||
hdlc_device *hdlc = dev_to_hdlc(pvc->master);
|
hdlc_device *hdlc = dev_to_hdlc(pvc->master);
|
||||||
if (hdlc->state.fr.settings.lmi == LMI_NONE)
|
if (hdlc->state.fr.settings.lmi == LMI_NONE)
|
||||||
pvc->state.active = hdlc->carrier;
|
pvc->state.active = netif_carrier_ok(pvc->master);
|
||||||
|
|
||||||
pvc_carrier(pvc->state.active, pvc);
|
pvc_carrier(pvc->state.active, pvc);
|
||||||
hdlc->state.fr.dce_changed = 1;
|
hdlc->state.fr.dce_changed = 1;
|
||||||
|
@ -545,11 +545,7 @@ static void fr_set_link_state(int reliable, struct net_device *dev)
|
||||||
|
|
||||||
hdlc->state.fr.reliable = reliable;
|
hdlc->state.fr.reliable = reliable;
|
||||||
if (reliable) {
|
if (reliable) {
|
||||||
#if 0
|
netif_dormant_off(dev);
|
||||||
if (!netif_carrier_ok(dev))
|
|
||||||
netif_carrier_on(dev);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
hdlc->state.fr.n391cnt = 0; /* Request full status */
|
hdlc->state.fr.n391cnt = 0; /* Request full status */
|
||||||
hdlc->state.fr.dce_changed = 1;
|
hdlc->state.fr.dce_changed = 1;
|
||||||
|
|
||||||
|
@ -562,11 +558,7 @@ static void fr_set_link_state(int reliable, struct net_device *dev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
#if 0
|
netif_dormant_on(dev);
|
||||||
if (netif_carrier_ok(dev))
|
|
||||||
netif_carrier_off(dev);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
while (pvc) { /* Deactivate all PVCs */
|
while (pvc) { /* Deactivate all PVCs */
|
||||||
pvc_carrier(0, pvc);
|
pvc_carrier(0, pvc);
|
||||||
pvc->state.exist = pvc->state.active = 0;
|
pvc->state.exist = pvc->state.active = 0;
|
||||||
|
|
|
@ -34,10 +34,11 @@
|
||||||
#include <linux/inetdevice.h>
|
#include <linux/inetdevice.h>
|
||||||
#include <linux/lapb.h>
|
#include <linux/lapb.h>
|
||||||
#include <linux/rtnetlink.h>
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <linux/notifier.h>
|
||||||
#include <linux/hdlc.h>
|
#include <linux/hdlc.h>
|
||||||
|
|
||||||
|
|
||||||
static const char* version = "HDLC support module revision 1.18";
|
static const char* version = "HDLC support module revision 1.19";
|
||||||
|
|
||||||
#undef DEBUG_LINK
|
#undef DEBUG_LINK
|
||||||
|
|
||||||
|
@ -73,57 +74,51 @@ static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void __hdlc_set_carrier_on(struct net_device *dev)
|
static inline void hdlc_proto_start(struct net_device *dev)
|
||||||
{
|
{
|
||||||
hdlc_device *hdlc = dev_to_hdlc(dev);
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
||||||
if (hdlc->proto.start)
|
if (hdlc->proto.start)
|
||||||
return hdlc->proto.start(dev);
|
return hdlc->proto.start(dev);
|
||||||
#if 0
|
|
||||||
#ifdef DEBUG_LINK
|
|
||||||
if (netif_carrier_ok(dev))
|
|
||||||
printk(KERN_ERR "hdlc_set_carrier_on(): already on\n");
|
|
||||||
#endif
|
|
||||||
netif_carrier_on(dev);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void __hdlc_set_carrier_off(struct net_device *dev)
|
static inline void hdlc_proto_stop(struct net_device *dev)
|
||||||
{
|
{
|
||||||
hdlc_device *hdlc = dev_to_hdlc(dev);
|
hdlc_device *hdlc = dev_to_hdlc(dev);
|
||||||
if (hdlc->proto.stop)
|
if (hdlc->proto.stop)
|
||||||
return hdlc->proto.stop(dev);
|
return hdlc->proto.stop(dev);
|
||||||
|
|
||||||
#if 0
|
|
||||||
#ifdef DEBUG_LINK
|
|
||||||
if (!netif_carrier_ok(dev))
|
|
||||||
printk(KERN_ERR "hdlc_set_carrier_off(): already off\n");
|
|
||||||
#endif
|
|
||||||
netif_carrier_off(dev);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void hdlc_set_carrier(int on, struct net_device *dev)
|
static int hdlc_device_event(struct notifier_block *this, unsigned long event,
|
||||||
|
void *ptr)
|
||||||
{
|
{
|
||||||
hdlc_device *hdlc = dev_to_hdlc(dev);
|
struct net_device *dev = ptr;
|
||||||
|
hdlc_device *hdlc;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
on = on ? 1 : 0;
|
int on;
|
||||||
|
|
||||||
|
if (dev->get_stats != hdlc_get_stats)
|
||||||
|
return NOTIFY_DONE; /* not an HDLC device */
|
||||||
|
|
||||||
|
if (event != NETDEV_CHANGE)
|
||||||
|
return NOTIFY_DONE; /* Only interrested in carrier changes */
|
||||||
|
|
||||||
|
on = netif_carrier_ok(dev);
|
||||||
|
|
||||||
#ifdef DEBUG_LINK
|
#ifdef DEBUG_LINK
|
||||||
printk(KERN_DEBUG "hdlc_set_carrier %i\n", on);
|
printk(KERN_DEBUG "%s: hdlc_device_event NETDEV_CHANGE, carrier %i\n",
|
||||||
|
dev->name, on);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
hdlc = dev_to_hdlc(dev);
|
||||||
spin_lock_irqsave(&hdlc->state_lock, flags);
|
spin_lock_irqsave(&hdlc->state_lock, flags);
|
||||||
|
|
||||||
if (hdlc->carrier == on)
|
if (hdlc->carrier == on)
|
||||||
goto carrier_exit; /* no change in DCD line level */
|
goto carrier_exit; /* no change in DCD line level */
|
||||||
|
|
||||||
#ifdef DEBUG_LINK
|
|
||||||
printk(KERN_INFO "%s: carrier %s\n", dev->name, on ? "ON" : "off");
|
|
||||||
#endif
|
|
||||||
hdlc->carrier = on;
|
hdlc->carrier = on;
|
||||||
|
|
||||||
if (!hdlc->open)
|
if (!hdlc->open)
|
||||||
|
@ -131,14 +126,15 @@ void hdlc_set_carrier(int on, struct net_device *dev)
|
||||||
|
|
||||||
if (hdlc->carrier) {
|
if (hdlc->carrier) {
|
||||||
printk(KERN_INFO "%s: Carrier detected\n", dev->name);
|
printk(KERN_INFO "%s: Carrier detected\n", dev->name);
|
||||||
__hdlc_set_carrier_on(dev);
|
hdlc_proto_start(dev);
|
||||||
} else {
|
} else {
|
||||||
printk(KERN_INFO "%s: Carrier lost\n", dev->name);
|
printk(KERN_INFO "%s: Carrier lost\n", dev->name);
|
||||||
__hdlc_set_carrier_off(dev);
|
hdlc_proto_stop(dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
carrier_exit:
|
carrier_exit:
|
||||||
spin_unlock_irqrestore(&hdlc->state_lock, flags);
|
spin_unlock_irqrestore(&hdlc->state_lock, flags);
|
||||||
|
return NOTIFY_DONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,7 +161,7 @@ int hdlc_open(struct net_device *dev)
|
||||||
|
|
||||||
if (hdlc->carrier) {
|
if (hdlc->carrier) {
|
||||||
printk(KERN_INFO "%s: Carrier detected\n", dev->name);
|
printk(KERN_INFO "%s: Carrier detected\n", dev->name);
|
||||||
__hdlc_set_carrier_on(dev);
|
hdlc_proto_start(dev);
|
||||||
} else
|
} else
|
||||||
printk(KERN_INFO "%s: No carrier\n", dev->name);
|
printk(KERN_INFO "%s: No carrier\n", dev->name);
|
||||||
|
|
||||||
|
@ -190,7 +186,7 @@ void hdlc_close(struct net_device *dev)
|
||||||
|
|
||||||
hdlc->open = 0;
|
hdlc->open = 0;
|
||||||
if (hdlc->carrier)
|
if (hdlc->carrier)
|
||||||
__hdlc_set_carrier_off(dev);
|
hdlc_proto_stop(dev);
|
||||||
|
|
||||||
spin_unlock_irq(&hdlc->state_lock);
|
spin_unlock_irq(&hdlc->state_lock);
|
||||||
|
|
||||||
|
@ -303,7 +299,6 @@ MODULE_LICENSE("GPL v2");
|
||||||
|
|
||||||
EXPORT_SYMBOL(hdlc_open);
|
EXPORT_SYMBOL(hdlc_open);
|
||||||
EXPORT_SYMBOL(hdlc_close);
|
EXPORT_SYMBOL(hdlc_close);
|
||||||
EXPORT_SYMBOL(hdlc_set_carrier);
|
|
||||||
EXPORT_SYMBOL(hdlc_ioctl);
|
EXPORT_SYMBOL(hdlc_ioctl);
|
||||||
EXPORT_SYMBOL(hdlc_setup);
|
EXPORT_SYMBOL(hdlc_setup);
|
||||||
EXPORT_SYMBOL(alloc_hdlcdev);
|
EXPORT_SYMBOL(alloc_hdlcdev);
|
||||||
|
@ -315,9 +310,18 @@ static struct packet_type hdlc_packet_type = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct notifier_block hdlc_notifier = {
|
||||||
|
.notifier_call = hdlc_device_event,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
static int __init hdlc_module_init(void)
|
static int __init hdlc_module_init(void)
|
||||||
{
|
{
|
||||||
|
int result;
|
||||||
|
|
||||||
printk(KERN_INFO "%s\n", version);
|
printk(KERN_INFO "%s\n", version);
|
||||||
|
if ((result = register_netdevice_notifier(&hdlc_notifier)) != 0)
|
||||||
|
return result;
|
||||||
dev_add_pack(&hdlc_packet_type);
|
dev_add_pack(&hdlc_packet_type);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -327,6 +331,7 @@ static int __init hdlc_module_init(void)
|
||||||
static void __exit hdlc_module_exit(void)
|
static void __exit hdlc_module_exit(void)
|
||||||
{
|
{
|
||||||
dev_remove_pack(&hdlc_packet_type);
|
dev_remove_pack(&hdlc_packet_type);
|
||||||
|
unregister_netdevice_notifier(&hdlc_notifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,10 @@ static inline void wanxl_cable_intr(port_t *port)
|
||||||
printk(KERN_INFO "%s: %s%s module, %s cable%s%s\n",
|
printk(KERN_INFO "%s: %s%s module, %s cable%s%s\n",
|
||||||
port->dev->name, pm, dte, cable, dsr, dcd);
|
port->dev->name, pm, dte, cable, dsr, dcd);
|
||||||
|
|
||||||
hdlc_set_carrier(value & STATUS_CABLE_DCD, port->dev);
|
if (value & STATUS_CABLE_DCD)
|
||||||
|
netif_carrier_on(port->dev);
|
||||||
|
else
|
||||||
|
netif_carrier_off(port->dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -224,8 +224,6 @@ static __inline__ void debug_frame(const struct sk_buff *skb)
|
||||||
int hdlc_open(struct net_device *dev);
|
int hdlc_open(struct net_device *dev);
|
||||||
/* Must be called by hardware driver when HDLC device is being closed */
|
/* Must be called by hardware driver when HDLC device is being closed */
|
||||||
void hdlc_close(struct net_device *dev);
|
void hdlc_close(struct net_device *dev);
|
||||||
/* Called by hardware driver when DCD line level changes */
|
|
||||||
void hdlc_set_carrier(int on, struct net_device *dev);
|
|
||||||
|
|
||||||
/* May be used by hardware driver to gain control over HDLC device */
|
/* May be used by hardware driver to gain control over HDLC device */
|
||||||
static __inline__ void hdlc_proto_detach(hdlc_device *hdlc)
|
static __inline__ void hdlc_proto_detach(hdlc_device *hdlc)
|
||||||
|
|
Загрузка…
Ссылка в новой задаче