blob: cb14642d97aa8300a12e37238933b91271077d9c [file] [log] [blame]
/*
* linux/drivers/s390/net/qeth_main.c
*
* Linux on zSeries OSA Express and HiperSockets support
*
* Copyright 2000,2003 IBM Corporation
*
* Author(s): Original Code written by
* Utz Bacher (utz.bacher@de.ibm.com)
* Rewritten by
* Frank Pavlic (fpavlic@de.ibm.com) and
* Thomas Spatzier <tspat@de.ibm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/ip.h>
#include <linux/inetdevice.h>
#include <linux/netdevice.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/igmp.h>
#include <linux/init.h>
#include <linux/reboot.h>
#include <linux/mii.h>
#include <linux/rcupdate.h>
#include <linux/ethtool.h>
#include <net/arp.h>
#include <net/ip.h>
#include <net/route.h>
#include <asm/ebcdic.h>
#include <asm/io.h>
#include <asm/qeth.h>
#include <asm/timex.h>
#include <asm/semaphore.h>
#include <asm/uaccess.h>
#include <asm/s390_rdev.h>
#include "qeth.h"
#include "qeth_mpc.h"
#include "qeth_fs.h"
#include "qeth_eddp.h"
#include "qeth_tso.h"
static const char *version = "qeth S/390 OSA-Express driver";
/**
* Debug Facility Stuff
*/
static debug_info_t *qeth_dbf_setup = NULL;
static debug_info_t *qeth_dbf_data = NULL;
static debug_info_t *qeth_dbf_misc = NULL;
static debug_info_t *qeth_dbf_control = NULL;
debug_info_t *qeth_dbf_trace = NULL;
static debug_info_t *qeth_dbf_sense = NULL;
static debug_info_t *qeth_dbf_qerr = NULL;
DEFINE_PER_CPU(char[256], qeth_dbf_txt_buf);
/**
* some more definitions and declarations
*/
static unsigned int known_devices[][10] = QETH_MODELLIST_ARRAY;
/* list of our cards */
struct qeth_card_list_struct qeth_card_list;
/*process list want to be notified*/
spinlock_t qeth_notify_lock;
struct list_head qeth_notify_list;
static void qeth_send_control_data_cb(struct qeth_channel *,
struct qeth_cmd_buffer *);
/**
* here we go with function implementation
*/
static void
qeth_init_qdio_info(struct qeth_card *card);
static int
qeth_init_qdio_queues(struct qeth_card *card);
static int
qeth_alloc_qdio_buffers(struct qeth_card *card);
static void
qeth_free_qdio_buffers(struct qeth_card *);
static void
qeth_clear_qdio_buffers(struct qeth_card *);
static void
qeth_clear_ip_list(struct qeth_card *, int, int);
static void
qeth_clear_ipacmd_list(struct qeth_card *);
static int
qeth_qdio_clear_card(struct qeth_card *, int);
static void
qeth_clear_working_pool_list(struct qeth_card *);
static void
qeth_clear_cmd_buffers(struct qeth_channel *);
static int
qeth_stop(struct net_device *);
static void
qeth_clear_ipato_list(struct qeth_card *);
static int
qeth_is_addr_covered_by_ipato(struct qeth_card *, struct qeth_ipaddr *);
static void
qeth_irq_tasklet(unsigned long);
static int
qeth_set_online(struct ccwgroup_device *);
static int
__qeth_set_online(struct ccwgroup_device *gdev, int recovery_mode);
static struct qeth_ipaddr *
qeth_get_addr_buffer(enum qeth_prot_versions);
static void
qeth_set_multicast_list(struct net_device *);
static void
qeth_setadp_promisc_mode(struct qeth_card *);
static void
qeth_notify_processes(void)
{
/*notify all registered processes */
struct qeth_notify_list_struct *n_entry;
QETH_DBF_TEXT(trace,3,"procnoti");
spin_lock(&qeth_notify_lock);
list_for_each_entry(n_entry, &qeth_notify_list, list) {
send_sig(n_entry->signum, n_entry->task, 1);
}
spin_unlock(&qeth_notify_lock);
}
int
qeth_notifier_unregister(struct task_struct *p)
{
struct qeth_notify_list_struct *n_entry, *tmp;
QETH_DBF_TEXT(trace, 2, "notunreg");
spin_lock(&qeth_notify_lock);
list_for_each_entry_safe(n_entry, tmp, &qeth_notify_list, list) {
if (n_entry->task == p) {
list_del(&n_entry->list);
kfree(n_entry);
goto out;
}
}
out:
spin_unlock(&qeth_notify_lock);
return 0;
}
int
qeth_notifier_register(struct task_struct *p, int signum)
{
struct qeth_notify_list_struct *n_entry;
/*check first if entry already exists*/
spin_lock(&qeth_notify_lock);
list_for_each_entry(n_entry, &qeth_notify_list, list) {
if (n_entry->task == p) {
n_entry->signum = signum;
spin_unlock(&qeth_notify_lock);
return 0;
}
}
spin_unlock(&qeth_notify_lock);
n_entry = (struct qeth_notify_list_struct *)
kmalloc(sizeof(struct qeth_notify_list_struct),GFP_KERNEL);
if (!n_entry)
return -ENOMEM;
n_entry->task = p;
n_entry->signum = signum;
spin_lock(&qeth_notify_lock);
list_add(&n_entry->list,&qeth_notify_list);
spin_unlock(&qeth_notify_lock);
return 0;
}
/**
* free channel command buffers
*/
static void
qeth_clean_channel(struct qeth_channel *channel)
{
int cnt;
QETH_DBF_TEXT(setup, 2, "freech");
for (cnt = 0; cnt < QETH_CMD_BUFFER_NO; cnt++)
kfree(channel->iob[cnt].data);
}
/**
* free card
*/
static void
qeth_free_card(struct qeth_card *card)
{
QETH_DBF_TEXT(setup, 2, "freecrd");
QETH_DBF_HEX(setup, 2, &card, sizeof(void *));
qeth_clean_channel(&card->read);
qeth_clean_channel(&card->write);
if (card->dev)
free_netdev(card->dev);
qeth_clear_ip_list(card, 0, 0);
qeth_clear_ipato_list(card);
kfree(card->ip_tbd_list);
qeth_free_qdio_buffers(card);
kfree(card);
}
/**
* alloc memory for command buffer per channel
*/
static int
qeth_setup_channel(struct qeth_channel *channel)
{
int cnt;
QETH_DBF_TEXT(setup, 2, "setupch");
for (cnt=0; cnt < QETH_CMD_BUFFER_NO; cnt++) {
channel->iob[cnt].data = (char *)
kmalloc(QETH_BUFSIZE, GFP_DMA|GFP_KERNEL);
if (channel->iob[cnt].data == NULL)
break;
channel->iob[cnt].state = BUF_STATE_FREE;
channel->iob[cnt].channel = channel;
channel->iob[cnt].callback = qeth_send_control_data_cb;
channel->iob[cnt].rc = 0;
}
if (cnt < QETH_CMD_BUFFER_NO) {
while (cnt-- > 0)
kfree(channel->iob[cnt].data);
return -ENOMEM;
}
channel->buf_no = 0;
channel->io_buf_no = 0;
atomic_set(&channel->irq_pending, 0);
spin_lock_init(&channel->iob_lock);
init_waitqueue_head(&channel->wait_q);
channel->irq_tasklet.data = (unsigned long) channel;
channel->irq_tasklet.func = qeth_irq_tasklet;
return 0;
}
/**
* alloc memory for card structure
*/
static struct qeth_card *
qeth_alloc_card(void)
{
struct qeth_card *card;
QETH_DBF_TEXT(setup, 2, "alloccrd");
card = kzalloc(sizeof(struct qeth_card), GFP_DMA|GFP_KERNEL);
if (!card)
return NULL;
QETH_DBF_HEX(setup, 2, &card, sizeof(void *));
if (qeth_setup_channel(&card->read)) {
kfree(card);
return NULL;
}
if (qeth_setup_channel(&card->write)) {
qeth_clean_channel(&card->read);
kfree(card);
return NULL;
}
return card;
}
static long
__qeth_check_irb_error(struct ccw_device *cdev, struct irb *irb)
{
if (!IS_ERR(irb))
return 0;
switch (PTR_ERR(irb)) {
case -EIO:
PRINT_WARN("i/o-error on device %s\n", cdev->dev.bus_id);
QETH_DBF_TEXT(trace, 2, "ckirberr");
QETH_DBF_TEXT_(trace, 2, " rc%d", -EIO);
break;
case -ETIMEDOUT:
PRINT_WARN("timeout on device %s\n", cdev->dev.bus_id);
QETH_DBF_TEXT(trace, 2, "ckirberr");
QETH_DBF_TEXT_(trace, 2, " rc%d", -ETIMEDOUT);
break;
default:
PRINT_WARN("unknown error %ld on device %s\n", PTR_ERR(irb),
cdev->dev.bus_id);
QETH_DBF_TEXT(trace, 2, "ckirberr");
QETH_DBF_TEXT(trace, 2, " rc???");
}
return PTR_ERR(irb);
}
static int
qeth_get_problem(struct ccw_device *cdev, struct irb *irb)
{
int dstat,cstat;
char *sense;
sense = (char *) irb->ecw;
cstat = irb->scsw.cstat;
dstat = irb->scsw.dstat;
if (cstat & (SCHN_STAT_CHN_CTRL_CHK | SCHN_STAT_INTF_CTRL_CHK |
SCHN_STAT_CHN_DATA_CHK | SCHN_STAT_CHAIN_CHECK |
SCHN_STAT_PROT_CHECK | SCHN_STAT_PROG_CHECK)) {
QETH_DBF_TEXT(trace,2, "CGENCHK");
PRINT_WARN("check on device %s, dstat=x%x, cstat=x%x ",
cdev->dev.bus_id, dstat, cstat);
HEXDUMP16(WARN, "irb: ", irb);
HEXDUMP16(WARN, "irb: ", ((char *) irb) + 32);
return 1;
}
if (dstat & DEV_STAT_UNIT_CHECK) {
if (sense[SENSE_RESETTING_EVENT_BYTE] &
SENSE_RESETTING_EVENT_FLAG) {
QETH_DBF_TEXT(trace,2,"REVIND");
return 1;
}
if (sense[SENSE_COMMAND_REJECT_BYTE] &
SENSE_COMMAND_REJECT_FLAG) {
QETH_DBF_TEXT(trace,2,"CMDREJi");
return 0;
}
if ((sense[2] == 0xaf) && (sense[3] == 0xfe)) {
QETH_DBF_TEXT(trace,2,"AFFE");
return 1;
}
if ((!sense[0]) && (!sense[1]) && (!sense[2]) && (!sense[3])) {
QETH_DBF_TEXT(trace,2,"ZEROSEN");
return 0;
}
QETH_DBF_TEXT(trace,2,"DGENCHK");
return 1;
}
return 0;
}
static int qeth_issue_next_read(struct qeth_card *);
/**
* interrupt handler
*/
static void
qeth_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb)
{
int rc;
int cstat,dstat;
struct qeth_cmd_buffer *buffer;
struct qeth_channel *channel;
struct qeth_card *card;
QETH_DBF_TEXT(trace,5,"irq");
if (__qeth_check_irb_error(cdev, irb))
return;
cstat = irb->scsw.cstat;
dstat = irb->scsw.dstat;
card = CARD_FROM_CDEV(cdev);
if (!card)
return;
if (card->read.ccwdev == cdev){
channel = &card->read;
QETH_DBF_TEXT(trace,5,"read");
} else if (card->write.ccwdev == cdev) {
channel = &card->write;
QETH_DBF_TEXT(trace,5,"write");
} else {
channel = &card->data;
QETH_DBF_TEXT(trace,5,"data");
}
atomic_set(&channel->irq_pending, 0);
if (irb->scsw.fctl & (SCSW_FCTL_CLEAR_FUNC))
channel->state = CH_STATE_STOPPED;
if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC))
channel->state = CH_STATE_HALTED;
/*let's wake up immediately on data channel*/
if ((channel == &card->data) && (intparm != 0))
goto out;
if (intparm == QETH_CLEAR_CHANNEL_PARM) {
QETH_DBF_TEXT(trace, 6, "clrchpar");
/* we don't have to handle this further */
intparm = 0;
}
if (intparm == QETH_HALT_CHANNEL_PARM) {
QETH_DBF_TEXT(trace, 6, "hltchpar");
/* we don't have to handle this further */
intparm = 0;
}
if ((dstat & DEV_STAT_UNIT_EXCEP) ||
(dstat & DEV_STAT_UNIT_CHECK) ||
(cstat)) {
if (irb->esw.esw0.erw.cons) {
/* TODO: we should make this s390dbf */
PRINT_WARN("sense data available on channel %s.\n",
CHANNEL_ID(channel));
PRINT_WARN(" cstat 0x%X\n dstat 0x%X\n", cstat, dstat);
HEXDUMP16(WARN,"irb: ",irb);
HEXDUMP16(WARN,"sense data: ",irb->ecw);
}
rc = qeth_get_problem(cdev,irb);
if (rc) {
qeth_schedule_recovery(card);
goto out;
}
}
if (intparm) {
buffer = (struct qeth_cmd_buffer *) __va((addr_t)intparm);
buffer->state = BUF_STATE_PROCESSED;
}
if (channel == &card->data)
return;
if (channel == &card->read &&
channel->state == CH_STATE_UP)
qeth_issue_next_read(card);
tasklet_schedule(&channel->irq_tasklet);
return;
out:
wake_up(&card->wait_q);
}
/**
* tasklet function scheduled from irq handler
*/
static void
qeth_irq_tasklet(unsigned long data)
{
struct qeth_card *card;
struct qeth_channel *channel;
struct qeth_cmd_buffer *iob;
__u8 index;
QETH_DBF_TEXT(trace,5,"irqtlet");
channel = (struct qeth_channel *) data;
iob = channel->iob;
index = channel->buf_no;
card = CARD_FROM_CDEV(channel->ccwdev);
while (iob[index].state == BUF_STATE_PROCESSED) {
if (iob[index].callback !=NULL) {
iob[index].callback(channel,iob + index);
}
index = (index + 1) % QETH_CMD_BUFFER_NO;
}
channel->buf_no = index;
wake_up(&card->wait_q);
}
static int qeth_stop_card(struct qeth_card *, int);
static int
__qeth_set_offline(struct ccwgroup_device *cgdev, int recovery_mode)
{
struct qeth_card *card = (struct qeth_card *) cgdev->dev.driver_data;
int rc = 0, rc2 = 0, rc3 = 0;
enum qeth_card_states recover_flag;
QETH_DBF_TEXT(setup, 3, "setoffl");
QETH_DBF_HEX(setup, 3, &card, sizeof(void *));
if (card->dev && netif_carrier_ok(card->dev))
netif_carrier_off(card->dev);
recover_flag = card->state;
if (qeth_stop_card(card, recovery_mode) == -ERESTARTSYS){
PRINT_WARN("Stopping card %s interrupted by user!\n",
CARD_BUS_ID(card));
return -ERESTARTSYS;
}
rc = ccw_device_set_offline(CARD_DDEV(card));
rc2 = ccw_device_set_offline(CARD_WDEV(card));
rc3 = ccw_device_set_offline(CARD_RDEV(card));
if (!rc)
rc = (rc2) ? rc2 : rc3;
if (rc)
QETH_DBF_TEXT_(setup, 2, "1err%d", rc);
if (recover_flag == CARD_STATE_UP)
card->state = CARD_STATE_RECOVER;
qeth_notify_processes();
return 0;
}
static int
qeth_set_offline(struct ccwgroup_device *cgdev)
{
return __qeth_set_offline(cgdev, 0);
}
static int
qeth_wait_for_threads(struct qeth_card *card, unsigned long threads);
static void
qeth_remove_device(struct ccwgroup_device *cgdev)
{
struct qeth_card *card = (struct qeth_card *) cgdev->dev.driver_data;
unsigned long flags;
QETH_DBF_TEXT(setup, 3, "rmdev");
QETH_DBF_HEX(setup, 3, &card, sizeof(void *));
if (!card)
return;
if (qeth_wait_for_threads(card, 0xffffffff))
return;
if (cgdev->state == CCWGROUP_ONLINE){
card->use_hard_stop = 1;
qeth_set_offline(cgdev);
}
/* remove form our internal list */
write_lock_irqsave(&qeth_card_list.rwlock, flags);
list_del(&card->list);
write_unlock_irqrestore(&qeth_card_list.rwlock, flags);
if (card->dev)
unregister_netdev(card->dev);
qeth_remove_device_attributes(&cgdev->dev);
qeth_free_card(card);
cgdev->dev.driver_data = NULL;
put_device(&cgdev->dev);
}
static int
qeth_register_addr_entry(struct qeth_card *, struct qeth_ipaddr *);
static int
qeth_deregister_addr_entry(struct qeth_card *, struct qeth_ipaddr *);
/**
* Add/remove address to/from card's ip list, i.e. try to add or remove
* reference to/from an IP address that is already registered on the card.
* Returns:
* 0 address was on card and its reference count has been adjusted,
* but is still > 0, so nothing has to be done
* also returns 0 if card was not on card and the todo was to delete
* the address -> there is also nothing to be done
* 1 address was not on card and the todo is to add it to the card's ip
* list
* -1 address was on card and its reference count has been decremented
* to <= 0 by the todo -> address must be removed from card
*/
static int
__qeth_ref_ip_on_card(struct qeth_card *card, struct qeth_ipaddr *todo,
struct qeth_ipaddr **__addr)
{
struct qeth_ipaddr *addr;
int found = 0;
list_for_each_entry(addr, &card->ip_list, entry) {
if (card->options.layer2) {
if ((addr->type == todo->type) &&
(memcmp(&addr->mac, &todo->mac,
OSA_ADDR_LEN) == 0)) {
found = 1;
break;
}
continue;
}
if ((addr->proto == QETH_PROT_IPV4) &&
(todo->proto == QETH_PROT_IPV4) &&
(addr->type == todo->type) &&
(addr->u.a4.addr == todo->u.a4.addr) &&
(addr->u.a4.mask == todo->u.a4.mask)) {
found = 1;
break;
}
if ((addr->proto == QETH_PROT_IPV6) &&
(todo->proto == QETH_PROT_IPV6) &&
(addr->type == todo->type) &&
(addr->u.a6.pfxlen == todo->u.a6.pfxlen) &&
(memcmp(&addr->u.a6.addr, &todo->u.a6.addr,
sizeof(struct in6_addr)) == 0)) {
found = 1;
break;
}
}
if (found) {
addr->users += todo->users;
if (addr->users <= 0){
*__addr = addr;
return -1;
} else {
/* for VIPA and RXIP limit refcount to 1 */
if (addr->type != QETH_IP_TYPE_NORMAL)
addr->users = 1;
return 0;
}
}
if (todo->users > 0) {
/* for VIPA and RXIP limit refcount to 1 */
if (todo->type != QETH_IP_TYPE_NORMAL)
todo->users = 1;
return 1;
} else
return 0;
}
static inline int
__qeth_address_exists_in_list(struct list_head *list, struct qeth_ipaddr *addr,
int same_type)
{
struct qeth_ipaddr *tmp;
list_for_each_entry(tmp, list, entry) {
if ((tmp->proto == QETH_PROT_IPV4) &&
(addr->proto == QETH_PROT_IPV4) &&
((same_type && (tmp->type == addr->type)) ||
(!same_type && (tmp->type != addr->type)) ) &&
(tmp->u.a4.addr == addr->u.a4.addr) ){
return 1;
}
if ((tmp->proto == QETH_PROT_IPV6) &&
(addr->proto == QETH_PROT_IPV6) &&
((same_type && (tmp->type == addr->type)) ||
(!same_type && (tmp->type != addr->type)) ) &&
(memcmp(&tmp->u.a6.addr, &addr->u.a6.addr,
sizeof(struct in6_addr)) == 0) ) {
return 1;
}
}
return 0;
}
/*
* Add IP to be added to todo list. If there is already an "add todo"
* in this list we just incremenent the reference count.
* Returns 0 if we just incremented reference count.
*/
static int
__qeth_insert_ip_todo(struct qeth_card *card, struct qeth_ipaddr *addr, int add)
{
struct qeth_ipaddr *tmp, *t;
int found = 0;
list_for_each_entry_safe(tmp, t, card->ip_tbd_list, entry) {
if ((addr->type == QETH_IP_TYPE_DEL_ALL_MC) &&
(tmp->type == QETH_IP_TYPE_DEL_ALL_MC))
return 0;
if (card->options.layer2) {
if ((tmp->type == addr->type) &&
(tmp->is_multicast == addr->is_multicast) &&
(memcmp(&tmp->mac, &addr->mac,
OSA_ADDR_LEN) == 0)) {
found = 1;
break;
}
continue;
}
if ((tmp->proto == QETH_PROT_IPV4) &&
(addr->proto == QETH_PROT_IPV4) &&
(tmp->type == addr->type) &&
(tmp->is_multicast == addr->is_multicast) &&
(tmp->u.a4.addr == addr->u.a4.addr) &&
(tmp->u.a4.mask == addr->u.a4.mask)) {
found = 1;
break;
}
if ((tmp->proto == QETH_PROT_IPV6) &&
(addr->proto == QETH_PROT_IPV6) &&
(tmp->type == addr->type) &&
(tmp->is_multicast == addr->is_multicast) &&
(tmp->u.a6.pfxlen == addr->u.a6.pfxlen) &&
(memcmp(&tmp->u.a6.addr, &addr->u.a6.addr,
sizeof(struct in6_addr)) == 0)) {
found = 1;
break;
}
}
if (found){
if (addr->users != 0)
tmp->users += addr->users;
else
tmp->users += add? 1:-1;
if (tmp->users == 0) {
list_del(&tmp->entry);
kfree(tmp);
}
return 0;
} else {
if (addr->type == QETH_IP_TYPE_DEL_ALL_MC)
list_add(&addr->entry, card->ip_tbd_list);
else {
if (addr->users == 0)
addr->users += add? 1:-1;
if (add && (addr->type == QETH_IP_TYPE_NORMAL) &&
qeth_is_addr_covered_by_ipato(card, addr)){
QETH_DBF_TEXT(trace, 2, "tkovaddr");
addr->set_flags |= QETH_IPA_SETIP_TAKEOVER_FLAG;
}
list_add_tail(&addr->entry, card->ip_tbd_list);
}
return 1;
}
}
/**
* Remove IP address from list
*/
static int
qeth_delete_ip(struct qeth_card *card, struct qeth_ipaddr *addr)
{
unsigned long flags;
int rc = 0;
QETH_DBF_TEXT(trace, 4, "delip");
if (card->options.layer2)
QETH_DBF_HEX(trace, 4, &addr->mac, 6);
else if (addr->proto == QETH_PROT_IPV4)
QETH_DBF_HEX(trace, 4, &addr->u.a4.addr, 4);
else {
QETH_DBF_HEX(trace, 4, &addr->u.a6.addr, 8);
QETH_DBF_HEX(trace, 4, ((char *)&addr->u.a6.addr) + 8, 8);
}
spin_lock_irqsave(&card->ip_lock, flags);
rc = __qeth_insert_ip_todo(card, addr, 0);
spin_unlock_irqrestore(&card->ip_lock, flags);
return rc;
}
static int
qeth_add_ip(struct qeth_card *card, struct qeth_ipaddr *addr)
{
unsigned long flags;
int rc = 0;
QETH_DBF_TEXT(trace, 4, "addip");
if (card->options.layer2)
QETH_DBF_HEX(trace, 4, &addr->mac, 6);
else if (addr->proto == QETH_PROT_IPV4)
QETH_DBF_HEX(trace, 4, &addr->u.a4.addr, 4);
else {
QETH_DBF_HEX(trace, 4, &addr->u.a6.addr, 8);
QETH_DBF_HEX(trace, 4, ((char *)&addr->u.a6.addr) + 8, 8);
}
spin_lock_irqsave(&card->ip_lock, flags);
rc = __qeth_insert_ip_todo(card, addr, 1);
spin_unlock_irqrestore(&card->ip_lock, flags);
return rc;
}
static inline void
__qeth_delete_all_mc(struct qeth_card *card, unsigned long *flags)
{
struct qeth_ipaddr *addr, *tmp;
int rc;
again:
list_for_each_entry_safe(addr, tmp, &card->ip_list, entry) {
if (addr->is_multicast) {
spin_unlock_irqrestore(&card->ip_lock, *flags);
rc = qeth_deregister_addr_entry(card, addr);
spin_lock_irqsave(&card->ip_lock, *flags);
if (!rc) {
list_del(&addr->entry);
kfree(addr);
goto again;
}
}
}
}
static void
qeth_set_ip_addr_list(struct qeth_card *card)
{
struct list_head *tbd_list;
struct qeth_ipaddr *todo, *addr;
unsigned long flags;
int rc;
QETH_DBF_TEXT(trace, 2, "sdiplist");
QETH_DBF_HEX(trace, 2, &card, sizeof(void *));
spin_lock_irqsave(&card->ip_lock, flags);
tbd_list = card->ip_tbd_list;
card->ip_tbd_list = kmalloc(sizeof(struct list_head), GFP_ATOMIC);
if (!card->ip_tbd_list) {
QETH_DBF_TEXT(trace, 0, "silnomem");
card->ip_tbd_list = tbd_list;
spin_unlock_irqrestore(&card->ip_lock, flags);
return;
} else
INIT_LIST_HEAD(card->ip_tbd_list);
while (!list_empty(tbd_list)){
todo = list_entry(tbd_list->next, struct qeth_ipaddr, entry);
list_del(&todo->entry);
if (todo->type == QETH_IP_TYPE_DEL_ALL_MC){
__qeth_delete_all_mc(card, &flags);
kfree(todo);
continue;
}
rc = __qeth_ref_ip_on_card(card, todo, &addr);
if (rc == 0) {
/* nothing to be done; only adjusted refcount */
kfree(todo);
} else if (rc == 1) {
/* new entry to be added to on-card list */
spin_unlock_irqrestore(&card->ip_lock, flags);
rc = qeth_register_addr_entry(card, todo);
spin_lock_irqsave(&card->ip_lock, flags);
if (!rc)
list_add_tail(&todo->entry, &card->ip_list);
else
kfree(todo);
} else if (rc == -1) {
/* on-card entry to be removed */
list_del_init(&addr->entry);
spin_unlock_irqrestore(&card->ip_lock, flags);
rc = qeth_deregister_addr_entry(card, addr);
spin_lock_irqsave(&card->ip_lock, flags);
if (!rc)
kfree(addr);
else
list_add_tail(&addr->entry, &card->ip_list);
kfree(todo);
}
}
spin_unlock_irqrestore(&card->ip_lock, flags);
kfree(tbd_list);
}
static void qeth_delete_mc_addresses(struct qeth_card *);
static void qeth_add_multicast_ipv4(struct qeth_card *);
static void qeth_layer2_add_multicast(struct qeth_card *);
#ifdef CONFIG_QETH_IPV6
static void qeth_add_multicast_ipv6(struct qeth_card *);
#endif
static inline int
qeth_set_thread_start_bit(struct qeth_card *card, unsigned long thread)
{
unsigned long flags;
spin_lock_irqsave(&card->thread_mask_lock, flags);
if ( !(card->thread_allowed_mask & thread) ||
(card->thread_start_mask & thread) ) {
spin_unlock_irqrestore(&card->thread_mask_lock, flags);
return -EPERM;
}
card->thread_start_mask |= thread;
spin_unlock_irqrestore(&card->thread_mask_lock, flags);
return 0;
}
static void
qeth_clear_thread_start_bit(struct qeth_card *card, unsigned long thread)
{
unsigned long flags;
spin_lock_irqsave(&card->thread_mask_lock, flags);
card->thread_start_mask &= ~thread;
spin_unlock_irqrestore(&card->thread_mask_lock, flags);
wake_up(&card->wait_q);
}
static void
qeth_clear_thread_running_bit(struct qeth_card *card, unsigned long thread)
{
unsigned long flags;
spin_lock_irqsave(&card->thread_mask_lock, flags);
card->thread_running_mask &= ~thread;
spin_unlock_irqrestore(&card->thread_mask_lock, flags);
wake_up(&card->wait_q);
}
static inline int
__qeth_do_run_thread(struct qeth_card *card, unsigned long thread)
{
unsigned long flags;
int rc = 0;
spin_lock_irqsave(&card->thread_mask_lock, flags);
if (card->thread_start_mask & thread){
if ((card->thread_allowed_mask & thread) &&
!(card->thread_running_mask & thread)){
rc = 1;
card->thread_start_mask &= ~thread;
card->thread_running_mask |= thread;
} else
rc = -EPERM;
}
spin_unlock_irqrestore(&card->thread_mask_lock, flags);
return rc;
}
static int
qeth_do_run_thread(struct qeth_card *card, unsigned long thread)
{
int rc = 0;
wait_event(card->wait_q,
(rc = __qeth_do_run_thread(card, thread)) >= 0);
return rc;
}
static int
qeth_register_ip_addresses(void *ptr)
{
struct qeth_card *card;
card = (struct qeth_card *) ptr;
daemonize("qeth_reg_ip");
QETH_DBF_TEXT(trace,4,"regipth1");
if (!qeth_do_run_thread(card, QETH_SET_IP_THREAD))
return 0;
QETH_DBF_TEXT(trace,4,"regipth2");
qeth_set_ip_addr_list(card);
qeth_clear_thread_running_bit(card, QETH_SET_IP_THREAD);
return 0;
}
/*
* Drive the SET_PROMISC_MODE thread
*/
static int
qeth_set_promisc_mode(void *ptr)
{
struct qeth_card *card = (struct qeth_card *) ptr;
daemonize("qeth_setprm");
QETH_DBF_TEXT(trace,4,"setprm1");
if (!qeth_do_run_thread(card, QETH_SET_PROMISC_MODE_THREAD))
return 0;
QETH_DBF_TEXT(trace,4,"setprm2");
qeth_setadp_promisc_mode(card);
qeth_clear_thread_running_bit(card, QETH_SET_PROMISC_MODE_THREAD);
return 0;
}
static int
qeth_recover(void *ptr)
{
struct qeth_card *card;
int rc = 0;
card = (struct qeth_card *) ptr;
daemonize("qeth_recover");
QETH_DBF_TEXT(trace,2,"recover1");
QETH_DBF_HEX(trace, 2, &card, sizeof(void *));
if (!qeth_do_run_thread(card, QETH_RECOVER_THREAD))
return 0;
QETH_DBF_TEXT(trace,2,"recover2");
PRINT_WARN("Recovery of device %s started ...\n",
CARD_BUS_ID(card));
card->use_hard_stop = 1;
__qeth_set_offline(card->gdev,1);
rc = __qeth_set_online(card->gdev,1);
if (!rc)
PRINT_INFO("Device %s successfully recovered!\n",
CARD_BUS_ID(card));
else
PRINT_INFO("Device %s could not be recovered!\n",
CARD_BUS_ID(card));
/* don't run another scheduled recovery */
qeth_clear_thread_start_bit(card, QETH_RECOVER_THREAD);
qeth_clear_thread_running_bit(card, QETH_RECOVER_THREAD);
return 0;
}
void
qeth_schedule_recovery(struct qeth_card *card)
{
QETH_DBF_TEXT(trace,2,"startrec");
if (qeth_set_thread_start_bit(card, QETH_RECOVER_THREAD) == 0)
schedule_work(&card->kernel_thread_starter);
}
static int
qeth_do_start_thread(struct qeth_card *card, unsigned long thread)
{
unsigned long flags;
int rc = 0;
spin_lock_irqsave(&card->thread_mask_lock, flags);
QETH_DBF_TEXT_(trace, 4, " %02x%02x%02x",
(u8) card->thread_start_mask,
(u8) card->thread_allowed_mask,
(u8) card->thread_running_mask);
rc = (card->thread_start_mask & thread);
spin_unlock_irqrestore(&card->thread_mask_lock, flags);
return rc;
}
static void
qeth_start_kernel_thread(struct qeth_card *card)
{
QETH_DBF_TEXT(trace , 2, "strthrd");
if (card->read.state != CH_STATE_UP &&
card->write.state != CH_STATE_UP)
return;
if (qeth_do_start_thread(card, QETH_SET_IP_THREAD))
kernel_thread(qeth_register_ip_addresses, (void *)card,SIGCHLD);
if (qeth_do_start_thread(card, QETH_SET_PROMISC_MODE_THREAD))
kernel_thread(qeth_set_promisc_mode, (void *)card, SIGCHLD);
if (qeth_do_start_thread(card, QETH_RECOVER_THREAD))
kernel_thread(qeth_recover, (void *) card, SIGCHLD);
}
static void
qeth_set_intial_options(struct qeth_card *card)
{
card->options.route4.type = NO_ROUTER;
#ifdef CONFIG_QETH_IPV6
card->options.route6.type = NO_ROUTER;
#endif /* QETH_IPV6 */
card->options.checksum_type = QETH_CHECKSUM_DEFAULT;
card->options.broadcast_mode = QETH_TR_BROADCAST_ALLRINGS;
card->options.macaddr_mode = QETH_TR_MACADDR_NONCANONICAL;
card->options.fake_broadcast = 0;
card->options.add_hhlen = DEFAULT_ADD_HHLEN;
card->options.fake_ll = 0;
if (card->info.type == QETH_CARD_TYPE_OSN)
card->options.layer2 = 1;
else
card->options.layer2 = 0;
}
/**
* initialize channels ,card and all state machines
*/
static int
qeth_setup_card(struct qeth_card *card)
{
QETH_DBF_TEXT(setup, 2, "setupcrd");
QETH_DBF_HEX(setup, 2, &card, sizeof(void *));
card->read.state = CH_STATE_DOWN;
card->write.state = CH_STATE_DOWN;
card->data.state = CH_STATE_DOWN;
card->state = CARD_STATE_DOWN;
card->lan_online = 0;
card->use_hard_stop = 0;
card->dev = NULL;
#ifdef CONFIG_QETH_VLAN
spin_lock_init(&card->vlanlock);
card->vlangrp = NULL;
#endif
spin_lock_init(&card->lock);
spin_lock_init(&card->ip_lock);
spin_lock_init(&card->thread_mask_lock);
card->thread_start_mask = 0;
card->thread_allowed_mask = 0;
card->thread_running_mask = 0;
INIT_WORK(&card->kernel_thread_starter,
(void *)qeth_start_kernel_thread,card);
INIT_LIST_HEAD(&card->ip_list);
card->ip_tbd_list = kmalloc(sizeof(struct list_head), GFP_KERNEL);
if (!card->ip_tbd_list) {
QETH_DBF_TEXT(setup, 0, "iptbdnom");
return -ENOMEM;
}
INIT_LIST_HEAD(card->ip_tbd_list);
INIT_LIST_HEAD(&card->cmd_waiter_list);
init_waitqueue_head(&card->wait_q);
/* intial options */
qeth_set_intial_options(card);
/* IP address takeover */
INIT_LIST_HEAD(&card->ipato.entries);
card->ipato.enabled = 0;
card->ipato.invert4 = 0;
card->ipato.invert6 = 0;
/* init QDIO stuff */
qeth_init_qdio_info(card);
return 0;
}
static int
is_1920_device (struct qeth_card *card)
{
int single_queue = 0;
struct ccw_device *ccwdev;
struct channelPath_dsc {
u8 flags;
u8 lsn;
u8 desc;
u8 chpid;
u8 swla;
u8 zeroes;
u8 chla;
u8 chpp;
} *chp_dsc;
QETH_DBF_TEXT(setup, 2, "chk_1920");
ccwdev = card->data.ccwdev;
chp_dsc = (struct channelPath_dsc *)ccw_device_get_chp_desc(ccwdev, 0);
if (chp_dsc != NULL) {
/* CHPP field bit 6 == 1 -> single queue */
single_queue = ((chp_dsc->chpp & 0x02) == 0x02);
kfree(chp_dsc);
}
QETH_DBF_TEXT_(setup, 2, "rc:%x", single_queue);
return single_queue;
}
static int
qeth_determine_card_type(struct qeth_card *card)
{
int i = 0;
QETH_DBF_TEXT(setup, 2, "detcdtyp");
card->qdio.do_prio_queueing = QETH_PRIOQ_DEFAULT;
card->qdio.default_out_queue = QETH_DEFAULT_QUEUE;
while (known_devices[i][4]) {
if ((CARD_RDEV(card)->id.dev_type == known_devices[i][2]) &&
(CARD_RDEV(card)->id.dev_model == known_devices[i][3])) {
card->info.type = known_devices[i][4];
card->qdio.no_out_queues = known_devices[i][8];
card->info.is_multicast_different = known_devices[i][9];
if (is_1920_device(card)) {
PRINT_INFO("Priority Queueing not able "
"due to hardware limitations!\n");
card->qdio.no_out_queues = 1;
card->qdio.default_out_queue = 0;
}
return 0;
}
i++;
}
card->info.type = QETH_CARD_TYPE_UNKNOWN;
PRINT_ERR("unknown card type on device %s\n", CARD_BUS_ID(card));
return -ENOENT;
}
static int
qeth_probe_device(struct ccwgroup_device *gdev)
{
struct qeth_card *card;
struct device *dev;
unsigned long flags;
int rc;
QETH_DBF_TEXT(setup, 2, "probedev");
dev = &gdev->dev;
if (!get_device(dev))
return -ENODEV;
QETH_DBF_TEXT_(setup, 2, "%s", gdev->dev.bus_id);
card = qeth_alloc_card();
if (!card) {
put_device(dev);
QETH_DBF_TEXT_(setup, 2, "1err%d", -ENOMEM);
return -ENOMEM;
}
card->read.ccwdev = gdev->cdev[0];
card->write.ccwdev = gdev->cdev[1];
card->data.ccwdev = gdev->cdev[2];
gdev->dev.driver_data = card;
card->gdev = gdev;
gdev->cdev[0]->handler = qeth_irq;
gdev->cdev[1]->handler = qeth_irq;
gdev->cdev[2]->handler = qeth_irq;
if ((rc = qeth_determine_card_type(card))){
PRINT_WARN("%s: not a valid card type\n", __func__);
QETH_DBF_TEXT_(setup, 2, "3err%d", rc);
put_device(dev);
qeth_free_card(card);
return rc;
}
if ((rc = qeth_setup_card(card))){
QETH_DBF_TEXT_(setup, 2, "2err%d", rc);
put_device(dev);
qeth_free_card(card);
return rc;
}
rc = qeth_create_device_attributes(dev);
if (rc) {
put_device(dev);
qeth_free_card(card);
return rc;
}
/* insert into our internal list */
write_lock_irqsave(&qeth_card_list.rwlock, flags);
list_add_tail(&card->list, &qeth_card_list.list);
write_unlock_irqrestore(&qeth_card_list.rwlock, flags);
return rc;
}
static int
qeth_get_unitaddr(struct qeth_card *card)
{
int length;
char *prcd;
int rc;
QETH_DBF_TEXT(setup, 2, "getunit");
rc = read_conf_data(CARD_DDEV(card), (void **) &prcd, &length);
if (rc) {
PRINT_ERR("read_conf_data for device %s returned %i\n",
CARD_DDEV_ID(card), rc);
return rc;
}
card->info.chpid = prcd[30];
card->info.unit_addr2 = prcd[31];
card->info.cula = prcd[63];
card->info.guestlan = ((prcd[0x10] == _ascebc['V']) &&
(prcd[0x11] == _ascebc['M']));
return 0;
}
static void
qeth_init_tokens(struct qeth_card *card)
{
card->token.issuer_rm_w = 0x00010103UL;
card->token.cm_filter_w = 0x00010108UL;
card->token.cm_connection_w = 0x0001010aUL;
card->token.ulp_filter_w = 0x0001010bUL;
card->token.ulp_connection_w = 0x0001010dUL;
}
static inline __u16
raw_devno_from_bus_id(char *id)
{
id += (strlen(id) - 4);
return (__u16) simple_strtoul(id, &id, 16);
}
/**
* setup channel
*/
static void
qeth_setup_ccw(struct qeth_channel *channel,unsigned char *iob, __u32 len)
{
struct qeth_card *card;
QETH_DBF_TEXT(trace, 4, "setupccw");
card = CARD_FROM_CDEV(channel->ccwdev);
if (channel == &card->read)
memcpy(&channel->ccw, READ_CCW, sizeof(struct ccw1));
else
memcpy(&channel->ccw, WRITE_CCW, sizeof(struct ccw1));
channel->ccw.count = len;
channel->ccw.cda = (__u32) __pa(iob);
}
/**
* get free buffer for ccws (IDX activation, lancmds,ipassists...)
*/
static struct qeth_cmd_buffer *
__qeth_get_buffer(struct qeth_channel *channel)
{
__u8 index;
QETH_DBF_TEXT(trace, 6, "getbuff");
index = channel->io_buf_no;
do {
if (channel->iob[index].state == BUF_STATE_FREE) {
channel->iob[index].state = BUF_STATE_LOCKED;
channel->io_buf_no = (channel->io_buf_no + 1) %
QETH_CMD_BUFFER_NO;
memset(channel->iob[index].data, 0, QETH_BUFSIZE);
return channel->iob + index;
}
index = (index + 1) % QETH_CMD_BUFFER_NO;
} while(index != channel->io_buf_no);
return NULL;
}
/**
* release command buffer
*/
static void
qeth_release_buffer(struct qeth_channel *channel, struct qeth_cmd_buffer *iob)
{
unsigned long flags;
QETH_DBF_TEXT(trace, 6, "relbuff");
spin_lock_irqsave(&channel->iob_lock, flags);
memset(iob->data, 0, QETH_BUFSIZE);
iob->state = BUF_STATE_FREE;
iob->callback = qeth_send_control_data_cb;
iob->rc = 0;
spin_unlock_irqrestore(&channel->iob_lock, flags);
}
static struct qeth_cmd_buffer *
qeth_get_buffer(struct qeth_channel *channel)
{
struct qeth_cmd_buffer *buffer = NULL;
unsigned long flags;
spin_lock_irqsave(&channel->iob_lock, flags);
buffer = __qeth_get_buffer(channel);
spin_unlock_irqrestore(&channel->iob_lock, flags);
return buffer;
}
static struct qeth_cmd_buffer *
qeth_wait_for_buffer(struct qeth_channel *channel)
{
struct qeth_cmd_buffer *buffer;
wait_event(channel->wait_q,
((buffer = qeth_get_buffer(channel)) != NULL));
return buffer;
}
static void
qeth_clear_cmd_buffers(struct qeth_channel *channel)
{
int cnt;
for (cnt=0; cnt < QETH_CMD_BUFFER_NO; cnt++)
qeth_release_buffer(channel,&channel->iob[cnt]);
channel->buf_no = 0;
channel->io_buf_no = 0;
}
/**
* start IDX for read and write channel
*/
static int
qeth_idx_activate_get_answer(struct qeth_channel *channel,
void (*idx_reply_cb)(struct qeth_channel *,
struct qeth_cmd_buffer *))
{
struct qeth_cmd_buffer *iob;
unsigned long flags;
int rc;
struct qeth_card *card;
QETH_DBF_TEXT(setup, 2, "idxanswr");
card = CARD_FROM_CDEV(channel->ccwdev);
iob = qeth_get_buffer(channel);
iob->callback = idx_reply_cb;
memcpy(&channel->ccw, READ_CCW, sizeof(struct ccw1));
channel->ccw.count = QETH_BUFSIZE;
channel->ccw.cda = (__u32) __pa(iob->data);
wait_event(card->wait_q,
atomic_cmpxchg(&channel->irq_pending, 0, 1) == 0);
QETH_DBF_TEXT(setup, 6, "noirqpnd");
spin_lock_irqsave(get_ccwdev_lock(channel->ccwdev), flags);
rc = ccw_device_start(channel->ccwdev,
&channel->ccw,(addr_t) iob, 0, 0);
spin_unlock_irqrestore(get_ccwdev_lock(channel->ccwdev), flags);
if (rc) {
PRINT_ERR("qeth: Error2 in activating channel rc=%d\n",rc);
QETH_DBF_TEXT_(setup, 2, "2err%d", rc);
atomic_set(&channel->irq_pending, 0);
wake_up(&card->wait_q);
return rc;
}
rc = wait_event_interruptible_timeout(card->wait_q,
channel->state == CH_STATE_UP, QETH_TIMEOUT);
if (rc == -ERESTARTSYS)
return rc;
if (channel->state != CH_STATE_UP){
rc = -ETIME;
QETH_DBF_TEXT_(setup, 2, "3err%d", rc);
qeth_clear_cmd_buffers(channel);
} else
rc = 0;
return rc;
}
static int
qeth_idx_activate_channel(struct qeth_channel *channel,
void (*idx_reply_cb)(struct qeth_channel *,
struct qeth_cmd_buffer *))
{
struct qeth_card *card;
struct qeth_cmd_buffer *iob;
unsigned long flags;
__u16 temp;
int rc;
card = CARD_FROM_CDEV(channel->ccwdev);
QETH_DBF_TEXT(setup, 2, "idxactch");
iob = qeth_get_buffer(channel);
iob->callback = idx_reply_cb;
memcpy(&channel->ccw, WRITE_CCW, sizeof(struct ccw1));
channel->ccw.count = IDX_ACTIVATE_SIZE;
channel->ccw.cda = (__u32) __pa(iob->data);
if (channel == &card->write) {
memcpy(iob->data, IDX_ACTIVATE_WRITE, IDX_ACTIVATE_SIZE);
memcpy(QETH_TRANSPORT_HEADER_SEQ_NO(iob->data),
&card->seqno.trans_hdr, QETH_SEQ_NO_LENGTH);
card->seqno.trans_hdr++;
} else {
memcpy(iob->data, IDX_ACTIVATE_READ, IDX_ACTIVATE_SIZE);
memcpy(QETH_TRANSPORT_HEADER_SEQ_NO(iob->data),
&card->seqno.trans_hdr, QETH_SEQ_NO_LENGTH);
}
memcpy(QETH_IDX_ACT_ISSUER_RM_TOKEN(iob->data),
&card->token.issuer_rm_w,QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_IDX_ACT_FUNC_LEVEL(iob->data),
&card->info.func_level,sizeof(__u16));
temp = raw_devno_from_bus_id(CARD_DDEV_ID(card));
memcpy(QETH_IDX_ACT_QDIO_DEV_CUA(iob->data), &temp, 2);
temp = (card->info.cula << 8) + card->info.unit_addr2;
memcpy(QETH_IDX_ACT_QDIO_DEV_REALADDR(iob->data), &temp, 2);
wait_event(card->wait_q,
atomic_cmpxchg(&channel->irq_pending, 0, 1) == 0);
QETH_DBF_TEXT(setup, 6, "noirqpnd");
spin_lock_irqsave(get_ccwdev_lock(channel->ccwdev), flags);
rc = ccw_device_start(channel->ccwdev,
&channel->ccw,(addr_t) iob, 0, 0);
spin_unlock_irqrestore(get_ccwdev_lock(channel->ccwdev), flags);
if (rc) {
PRINT_ERR("qeth: Error1 in activating channel. rc=%d\n",rc);
QETH_DBF_TEXT_(setup, 2, "1err%d", rc);
atomic_set(&channel->irq_pending, 0);
wake_up(&card->wait_q);
return rc;
}
rc = wait_event_interruptible_timeout(card->wait_q,
channel->state == CH_STATE_ACTIVATING, QETH_TIMEOUT);
if (rc == -ERESTARTSYS)
return rc;
if (channel->state != CH_STATE_ACTIVATING) {
PRINT_WARN("qeth: IDX activate timed out!\n");
QETH_DBF_TEXT_(setup, 2, "2err%d", -ETIME);
qeth_clear_cmd_buffers(channel);
return -ETIME;
}
return qeth_idx_activate_get_answer(channel,idx_reply_cb);
}
static int
qeth_peer_func_level(int level)
{
if ((level & 0xff) == 8)
return (level & 0xff) + 0x400;
if (((level >> 8) & 3) == 1)
return (level & 0xff) + 0x200;
return level;
}
static void
qeth_idx_write_cb(struct qeth_channel *channel, struct qeth_cmd_buffer *iob)
{
struct qeth_card *card;
__u16 temp;
QETH_DBF_TEXT(setup ,2, "idxwrcb");
if (channel->state == CH_STATE_DOWN) {
channel->state = CH_STATE_ACTIVATING;
goto out;
}
card = CARD_FROM_CDEV(channel->ccwdev);
if (!(QETH_IS_IDX_ACT_POS_REPLY(iob->data))) {
PRINT_ERR("IDX_ACTIVATE on write channel device %s: negative "
"reply\n", CARD_WDEV_ID(card));
goto out;
}
memcpy(&temp, QETH_IDX_ACT_FUNC_LEVEL(iob->data), 2);
if ((temp & ~0x0100) != qeth_peer_func_level(card->info.func_level)) {
PRINT_WARN("IDX_ACTIVATE on write channel device %s: "
"function level mismatch "
"(sent: 0x%x, received: 0x%x)\n",
CARD_WDEV_ID(card), card->info.func_level, temp);
goto out;
}
channel->state = CH_STATE_UP;
out:
qeth_release_buffer(channel, iob);
}
static int
qeth_check_idx_response(unsigned char *buffer)
{
if (!buffer)
return 0;
QETH_DBF_HEX(control, 2, buffer, QETH_DBF_CONTROL_LEN);
if ((buffer[2] & 0xc0) == 0xc0) {
PRINT_WARN("received an IDX TERMINATE "
"with cause code 0x%02x%s\n",
buffer[4],
((buffer[4] == 0x22) ?
" -- try another portname" : ""));
QETH_DBF_TEXT(trace, 2, "ckidxres");
QETH_DBF_TEXT(trace, 2, " idxterm");
QETH_DBF_TEXT_(trace, 2, " rc%d", -EIO);
return -EIO;
}
return 0;
}
static void
qeth_idx_read_cb(struct qeth_channel *channel, struct qeth_cmd_buffer *iob)
{
struct qeth_card *card;
__u16 temp;
QETH_DBF_TEXT(setup , 2, "idxrdcb");
if (channel->state == CH_STATE_DOWN) {
channel->state = CH_STATE_ACTIVATING;
goto out;
}
card = CARD_FROM_CDEV(channel->ccwdev);
if (qeth_check_idx_response(iob->data)) {
goto out;
}
if (!(QETH_IS_IDX_ACT_POS_REPLY(iob->data))) {
PRINT_ERR("IDX_ACTIVATE on read channel device %s: negative "
"reply\n", CARD_RDEV_ID(card));
goto out;
}
/**
* temporary fix for microcode bug
* to revert it,replace OR by AND
*/
if ( (!QETH_IDX_NO_PORTNAME_REQUIRED(iob->data)) ||
(card->info.type == QETH_CARD_TYPE_OSAE) )
card->info.portname_required = 1;
memcpy(&temp, QETH_IDX_ACT_FUNC_LEVEL(iob->data), 2);
if (temp != qeth_peer_func_level(card->info.func_level)) {
PRINT_WARN("IDX_ACTIVATE on read channel device %s: function "
"level mismatch (sent: 0x%x, received: 0x%x)\n",
CARD_RDEV_ID(card), card->info.func_level, temp);
goto out;
}
memcpy(&card->token.issuer_rm_r,
QETH_IDX_ACT_ISSUER_RM_TOKEN(iob->data),
QETH_MPC_TOKEN_LENGTH);
memcpy(&card->info.mcl_level[0],
QETH_IDX_REPLY_LEVEL(iob->data), QETH_MCL_LENGTH);
channel->state = CH_STATE_UP;
out:
qeth_release_buffer(channel,iob);
}
static int
qeth_issue_next_read(struct qeth_card *card)
{
int rc;
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(trace,5,"issnxrd");
if (card->read.state != CH_STATE_UP)
return -EIO;
iob = qeth_get_buffer(&card->read);
if (!iob) {
PRINT_WARN("issue_next_read failed: no iob available!\n");
return -ENOMEM;
}
qeth_setup_ccw(&card->read, iob->data, QETH_BUFSIZE);
wait_event(card->wait_q,
atomic_cmpxchg(&card->read.irq_pending, 0, 1) == 0);
QETH_DBF_TEXT(trace, 6, "noirqpnd");
rc = ccw_device_start(card->read.ccwdev, &card->read.ccw,
(addr_t) iob, 0, 0);
if (rc) {
PRINT_ERR("Error in starting next read ccw! rc=%i\n", rc);
atomic_set(&card->read.irq_pending, 0);
qeth_schedule_recovery(card);
wake_up(&card->wait_q);
}
return rc;
}
static struct qeth_reply *
qeth_alloc_reply(struct qeth_card *card)
{
struct qeth_reply *reply;
reply = kzalloc(sizeof(struct qeth_reply), GFP_ATOMIC);
if (reply){
atomic_set(&reply->refcnt, 1);
reply->card = card;
};
return reply;
}
static void
qeth_get_reply(struct qeth_reply *reply)
{
WARN_ON(atomic_read(&reply->refcnt) <= 0);
atomic_inc(&reply->refcnt);
}
static void
qeth_put_reply(struct qeth_reply *reply)
{
WARN_ON(atomic_read(&reply->refcnt) <= 0);
if (atomic_dec_and_test(&reply->refcnt))
kfree(reply);
}
static void
qeth_cmd_timeout(unsigned long data)
{
struct qeth_reply *reply, *list_reply, *r;
unsigned long flags;
reply = (struct qeth_reply *) data;
spin_lock_irqsave(&reply->card->lock, flags);
list_for_each_entry_safe(list_reply, r,
&reply->card->cmd_waiter_list, list) {
if (reply == list_reply){
qeth_get_reply(reply);
list_del_init(&reply->list);
spin_unlock_irqrestore(&reply->card->lock, flags);
reply->rc = -ETIME;
reply->received = 1;
wake_up(&reply->wait_q);
qeth_put_reply(reply);
return;
}
}
spin_unlock_irqrestore(&reply->card->lock, flags);
}
static struct qeth_ipa_cmd *
qeth_check_ipa_data(struct qeth_card *card, struct qeth_cmd_buffer *iob)
{
struct qeth_ipa_cmd *cmd = NULL;
QETH_DBF_TEXT(trace,5,"chkipad");
if (IS_IPA(iob->data)){
cmd = (struct qeth_ipa_cmd *) PDU_ENCAPSULATION(iob->data);
if (IS_IPA_REPLY(cmd))
return cmd;
else {
switch (cmd->hdr.command) {
case IPA_CMD_STOPLAN:
PRINT_WARN("Link failure on %s (CHPID 0x%X) - "
"there is a network problem or "
"someone pulled the cable or "
"disabled the port.\n",
QETH_CARD_IFNAME(card),
card->info.chpid);
card->lan_online = 0;
if (card->dev && netif_carrier_ok(card->dev))
netif_carrier_off(card->dev);
return NULL;
case IPA_CMD_STARTLAN:
PRINT_INFO("Link reestablished on %s "
"(CHPID 0x%X). Scheduling "
"IP address reset.\n",
QETH_CARD_IFNAME(card),
card->info.chpid);
qeth_schedule_recovery(card);
return NULL;
case IPA_CMD_MODCCID:
return cmd;
case IPA_CMD_REGISTER_LOCAL_ADDR:
QETH_DBF_TEXT(trace,3, "irla");
break;
case IPA_CMD_UNREGISTER_LOCAL_ADDR:
QETH_DBF_TEXT(trace,3, "urla");
break;
default:
PRINT_WARN("Received data is IPA "
"but not a reply!\n");
break;
}
}
}
return cmd;
}
/**
* wake all waiting ipa commands
*/
static void
qeth_clear_ipacmd_list(struct qeth_card *card)
{
struct qeth_reply *reply, *r;
unsigned long flags;
QETH_DBF_TEXT(trace, 4, "clipalst");
spin_lock_irqsave(&card->lock, flags);
list_for_each_entry_safe(reply, r, &card->cmd_waiter_list, list) {
qeth_get_reply(reply);
reply->rc = -EIO;
reply->received = 1;
list_del_init(&reply->list);
wake_up(&reply->wait_q);
qeth_put_reply(reply);
}
spin_unlock_irqrestore(&card->lock, flags);
}
static void
qeth_send_control_data_cb(struct qeth_channel *channel,
struct qeth_cmd_buffer *iob)
{
struct qeth_card *card;
struct qeth_reply *reply, *r;
struct qeth_ipa_cmd *cmd;
unsigned long flags;
int keep_reply;
QETH_DBF_TEXT(trace,4,"sndctlcb");
card = CARD_FROM_CDEV(channel->ccwdev);
if (qeth_check_idx_response(iob->data)) {
qeth_clear_ipacmd_list(card);
qeth_schedule_recovery(card);
goto out;
}
cmd = qeth_check_ipa_data(card, iob);
if ((cmd == NULL) && (card->state != CARD_STATE_DOWN))
goto out;
/*in case of OSN : check if cmd is set */
if (card->info.type == QETH_CARD_TYPE_OSN &&
cmd &&
cmd->hdr.command != IPA_CMD_STARTLAN &&
card->osn_info.assist_cb != NULL) {
card->osn_info.assist_cb(card->dev, cmd);
goto out;
}
spin_lock_irqsave(&card->lock, flags);
list_for_each_entry_safe(reply, r, &card->cmd_waiter_list, list) {
if ((reply->seqno == QETH_IDX_COMMAND_SEQNO) ||
((cmd) && (reply->seqno == cmd->hdr.seqno))) {
qeth_get_reply(reply);
list_del_init(&reply->list);
spin_unlock_irqrestore(&card->lock, flags);
keep_reply = 0;
if (reply->callback != NULL) {
if (cmd) {
reply->offset = (__u16)((char*)cmd -
(char *)iob->data);
keep_reply = reply->callback(card,
reply,
(unsigned long)cmd);
} else
keep_reply = reply->callback(card,
reply,
(unsigned long)iob);
}
if (cmd)
reply->rc = (u16) cmd->hdr.return_code;
else if (iob->rc)
reply->rc = iob->rc;
if (keep_reply) {
spin_lock_irqsave(&card->lock, flags);
list_add_tail(&reply->list,
&card->cmd_waiter_list);
spin_unlock_irqrestore(&card->lock, flags);
} else {
reply->received = 1;
wake_up(&reply->wait_q);
}
qeth_put_reply(reply);
goto out;
}
}
spin_unlock_irqrestore(&card->lock, flags);
out:
memcpy(&card->seqno.pdu_hdr_ack,
QETH_PDU_HEADER_SEQ_NO(iob->data),
QETH_SEQ_NO_LENGTH);
qeth_release_buffer(channel,iob);
}
static inline void
qeth_prepare_control_data(struct qeth_card *card, int len,
struct qeth_cmd_buffer *iob)
{
qeth_setup_ccw(&card->write,iob->data,len);
iob->callback = qeth_release_buffer;
memcpy(QETH_TRANSPORT_HEADER_SEQ_NO(iob->data),
&card->seqno.trans_hdr, QETH_SEQ_NO_LENGTH);
card->seqno.trans_hdr++;
memcpy(QETH_PDU_HEADER_SEQ_NO(iob->data),
&card->seqno.pdu_hdr, QETH_SEQ_NO_LENGTH);
card->seqno.pdu_hdr++;
memcpy(QETH_PDU_HEADER_ACK_SEQ_NO(iob->data),
&card->seqno.pdu_hdr_ack, QETH_SEQ_NO_LENGTH);
QETH_DBF_HEX(control, 2, iob->data, QETH_DBF_CONTROL_LEN);
}
static int
qeth_send_control_data(struct qeth_card *card, int len,
struct qeth_cmd_buffer *iob,
int (*reply_cb)
(struct qeth_card *, struct qeth_reply*, unsigned long),
void *reply_param)
{
int rc;
unsigned long flags;
struct qeth_reply *reply = NULL;
struct timer_list timer;
QETH_DBF_TEXT(trace, 2, "sendctl");
reply = qeth_alloc_reply(card);
if (!reply) {
PRINT_WARN("Could no alloc qeth_reply!\n");
return -ENOMEM;
}
reply->callback = reply_cb;
reply->param = reply_param;
if (card->state == CARD_STATE_DOWN)
reply->seqno = QETH_IDX_COMMAND_SEQNO;
else
reply->seqno = card->seqno.ipa++;
init_timer(&timer);
timer.function = qeth_cmd_timeout;
timer.data = (unsigned long) reply;
init_waitqueue_head(&reply->wait_q);
spin_lock_irqsave(&card->lock, flags);
list_add_tail(&reply->list, &card->cmd_waiter_list);
spin_unlock_irqrestore(&card->lock, flags);
QETH_DBF_HEX(control, 2, iob->data, QETH_DBF_CONTROL_LEN);
wait_event(card->wait_q,
atomic_cmpxchg(&card->write.irq_pending, 0, 1) == 0);
qeth_prepare_control_data(card, len, iob);
if (IS_IPA(iob->data))
timer.expires = jiffies + QETH_IPA_TIMEOUT;
else
timer.expires = jiffies + QETH_TIMEOUT;
QETH_DBF_TEXT(trace, 6, "noirqpnd");
spin_lock_irqsave(get_ccwdev_lock(card->write.ccwdev), flags);
rc = ccw_device_start(card->write.ccwdev, &card->write.ccw,
(addr_t) iob, 0, 0);
spin_unlock_irqrestore(get_ccwdev_lock(card->write.ccwdev), flags);
if (rc){
PRINT_WARN("qeth_send_control_data: "
"ccw_device_start rc = %i\n", rc);
QETH_DBF_TEXT_(trace, 2, " err%d", rc);
spin_lock_irqsave(&card->lock, flags);
list_del_init(&reply->list);
qeth_put_reply(reply);
spin_unlock_irqrestore(&card->lock, flags);
qeth_release_buffer(iob->channel, iob);
atomic_set(&card->write.irq_pending, 0);
wake_up(&card->wait_q);
return rc;
}
add_timer(&timer);
wait_event(reply->wait_q, reply->received);
del_timer_sync(&timer);
rc = reply->rc;
qeth_put_reply(reply);
return rc;
}
static int
qeth_osn_send_control_data(struct qeth_card *card, int len,
struct qeth_cmd_buffer *iob)
{
unsigned long flags;
int rc = 0;
QETH_DBF_TEXT(trace, 5, "osndctrd");
wait_event(card->wait_q,
atomic_cmpxchg(&card->write.irq_pending, 0, 1) == 0);
qeth_prepare_control_data(card, len, iob);
QETH_DBF_TEXT(trace, 6, "osnoirqp");
spin_lock_irqsave(get_ccwdev_lock(card->write.ccwdev), flags);
rc = ccw_device_start(card->write.ccwdev, &card->write.ccw,
(addr_t) iob, 0, 0);
spin_unlock_irqrestore(get_ccwdev_lock(card->write.ccwdev), flags);
if (rc){
PRINT_WARN("qeth_osn_send_control_data: "
"ccw_device_start rc = %i\n", rc);
QETH_DBF_TEXT_(trace, 2, " err%d", rc);
qeth_release_buffer(iob->channel, iob);
atomic_set(&card->write.irq_pending, 0);
wake_up(&card->wait_q);
}
return rc;
}
static inline void
qeth_prepare_ipa_cmd(struct qeth_card *card, struct qeth_cmd_buffer *iob,
char prot_type)
{
memcpy(iob->data, IPA_PDU_HEADER, IPA_PDU_HEADER_SIZE);
memcpy(QETH_IPA_CMD_PROT_TYPE(iob->data),&prot_type,1);
memcpy(QETH_IPA_CMD_DEST_ADDR(iob->data),
&card->token.ulp_connection_r, QETH_MPC_TOKEN_LENGTH);
}
static int
qeth_osn_send_ipa_cmd(struct qeth_card *card, struct qeth_cmd_buffer *iob,
int data_len)
{
u16 s1, s2;
QETH_DBF_TEXT(trace,4,"osndipa");
qeth_prepare_ipa_cmd(card, iob, QETH_PROT_OSN2);
s1 = (u16)(IPA_PDU_HEADER_SIZE + data_len);
s2 = (u16)data_len;
memcpy(QETH_IPA_PDU_LEN_TOTAL(iob->data), &s1, 2);
memcpy(QETH_IPA_PDU_LEN_PDU1(iob->data), &s2, 2);
memcpy(QETH_IPA_PDU_LEN_PDU2(iob->data), &s2, 2);
memcpy(QETH_IPA_PDU_LEN_PDU3(iob->data), &s2, 2);
return qeth_osn_send_control_data(card, s1, iob);
}
static int
qeth_send_ipa_cmd(struct qeth_card *card, struct qeth_cmd_buffer *iob,
int (*reply_cb)
(struct qeth_card *,struct qeth_reply*, unsigned long),
void *reply_param)
{
int rc;
char prot_type;
QETH_DBF_TEXT(trace,4,"sendipa");
if (card->options.layer2)
if (card->info.type == QETH_CARD_TYPE_OSN)
prot_type = QETH_PROT_OSN2;
else
prot_type = QETH_PROT_LAYER2;
else
prot_type = QETH_PROT_TCPIP;
qeth_prepare_ipa_cmd(card,iob,prot_type);
rc = qeth_send_control_data(card, IPA_CMD_LENGTH, iob,
reply_cb, reply_param);
return rc;
}
static int
qeth_cm_enable_cb(struct qeth_card *card, struct qeth_reply *reply,
unsigned long data)
{
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup, 2, "cmenblcb");
iob = (struct qeth_cmd_buffer *) data;
memcpy(&card->token.cm_filter_r,
QETH_CM_ENABLE_RESP_FILTER_TOKEN(iob->data),
QETH_MPC_TOKEN_LENGTH);
QETH_DBF_TEXT_(setup, 2, " rc%d", iob->rc);
return 0;
}
static int
qeth_cm_enable(struct qeth_card *card)
{
int rc;
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup,2,"cmenable");
iob = qeth_wait_for_buffer(&card->write);
memcpy(iob->data, CM_ENABLE, CM_ENABLE_SIZE);
memcpy(QETH_CM_ENABLE_ISSUER_RM_TOKEN(iob->data),
&card->token.issuer_rm_r, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_CM_ENABLE_FILTER_TOKEN(iob->data),
&card->token.cm_filter_w, QETH_MPC_TOKEN_LENGTH);
rc = qeth_send_control_data(card, CM_ENABLE_SIZE, iob,
qeth_cm_enable_cb, NULL);
return rc;
}
static int
qeth_cm_setup_cb(struct qeth_card *card, struct qeth_reply *reply,
unsigned long data)
{
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup, 2, "cmsetpcb");
iob = (struct qeth_cmd_buffer *) data;
memcpy(&card->token.cm_connection_r,
QETH_CM_SETUP_RESP_DEST_ADDR(iob->data),
QETH_MPC_TOKEN_LENGTH);
QETH_DBF_TEXT_(setup, 2, " rc%d", iob->rc);
return 0;
}
static int
qeth_cm_setup(struct qeth_card *card)
{
int rc;
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup,2,"cmsetup");
iob = qeth_wait_for_buffer(&card->write);
memcpy(iob->data, CM_SETUP, CM_SETUP_SIZE);
memcpy(QETH_CM_SETUP_DEST_ADDR(iob->data),
&card->token.issuer_rm_r, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_CM_SETUP_CONNECTION_TOKEN(iob->data),
&card->token.cm_connection_w, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_CM_SETUP_FILTER_TOKEN(iob->data),
&card->token.cm_filter_r, QETH_MPC_TOKEN_LENGTH);
rc = qeth_send_control_data(card, CM_SETUP_SIZE, iob,
qeth_cm_setup_cb, NULL);
return rc;
}
static int
qeth_ulp_enable_cb(struct qeth_card *card, struct qeth_reply *reply,
unsigned long data)
{
__u16 mtu, framesize;
__u16 len;
__u8 link_type;
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup, 2, "ulpenacb");
iob = (struct qeth_cmd_buffer *) data;
memcpy(&card->token.ulp_filter_r,
QETH_ULP_ENABLE_RESP_FILTER_TOKEN(iob->data),
QETH_MPC_TOKEN_LENGTH);
if (qeth_get_mtu_out_of_mpc(card->info.type)) {
memcpy(&framesize, QETH_ULP_ENABLE_RESP_MAX_MTU(iob->data), 2);
mtu = qeth_get_mtu_outof_framesize(framesize);
if (!mtu) {
iob->rc = -EINVAL;
QETH_DBF_TEXT_(setup, 2, " rc%d", iob->rc);
return 0;
}
card->info.max_mtu = mtu;
card->info.initial_mtu = mtu;
card->qdio.in_buf_size = mtu + 2 * PAGE_SIZE;
} else {
card->info.initial_mtu = qeth_get_initial_mtu_for_card(card);
card->info.max_mtu = qeth_get_max_mtu_for_card(card->info.type);
card->qdio.in_buf_size = QETH_IN_BUF_SIZE_DEFAULT;
}
memcpy(&len, QETH_ULP_ENABLE_RESP_DIFINFO_LEN(iob->data), 2);
if (len >= QETH_MPC_DIFINFO_LEN_INDICATES_LINK_TYPE) {
memcpy(&link_type,
QETH_ULP_ENABLE_RESP_LINK_TYPE(iob->data), 1);
card->info.link_type = link_type;
} else
card->info.link_type = 0;
QETH_DBF_TEXT_(setup, 2, " rc%d", iob->rc);
return 0;
}
static int
qeth_ulp_enable(struct qeth_card *card)
{
int rc;
char prot_type;
struct qeth_cmd_buffer *iob;
/*FIXME: trace view callbacks*/
QETH_DBF_TEXT(setup,2,"ulpenabl");
iob = qeth_wait_for_buffer(&card->write);
memcpy(iob->data, ULP_ENABLE, ULP_ENABLE_SIZE);
*(QETH_ULP_ENABLE_LINKNUM(iob->data)) =
(__u8) card->info.portno;
if (card->options.layer2)
if (card->info.type == QETH_CARD_TYPE_OSN)
prot_type = QETH_PROT_OSN2;
else
prot_type = QETH_PROT_LAYER2;
else
prot_type = QETH_PROT_TCPIP;
memcpy(QETH_ULP_ENABLE_PROT_TYPE(iob->data),&prot_type,1);
memcpy(QETH_ULP_ENABLE_DEST_ADDR(iob->data),
&card->token.cm_connection_r, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_ULP_ENABLE_FILTER_TOKEN(iob->data),
&card->token.ulp_filter_w, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_ULP_ENABLE_PORTNAME_AND_LL(iob->data),
card->info.portname, 9);
rc = qeth_send_control_data(card, ULP_ENABLE_SIZE, iob,
qeth_ulp_enable_cb, NULL);
return rc;
}
static inline __u16
__raw_devno_from_bus_id(char *id)
{
id += (strlen(id) - 4);
return (__u16) simple_strtoul(id, &id, 16);
}
static int
qeth_ulp_setup_cb(struct qeth_card *card, struct qeth_reply *reply,
unsigned long data)
{
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup, 2, "ulpstpcb");
iob = (struct qeth_cmd_buffer *) data;
memcpy(&card->token.ulp_connection_r,
QETH_ULP_SETUP_RESP_CONNECTION_TOKEN(iob->data),
QETH_MPC_TOKEN_LENGTH);
QETH_DBF_TEXT_(setup, 2, " rc%d", iob->rc);
return 0;
}
static int
qeth_ulp_setup(struct qeth_card *card)
{
int rc;
__u16 temp;
struct qeth_cmd_buffer *iob;
QETH_DBF_TEXT(setup,2,"ulpsetup");
iob = qeth_wait_for_buffer(&card->write);
memcpy(iob->data, ULP_SETUP, ULP_SETUP_SIZE);
memcpy(QETH_ULP_SETUP_DEST_ADDR(iob->data),
&card->token.cm_connection_r, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_ULP_SETUP_CONNECTION_TOKEN(iob->data),
&card->token.ulp_connection_w, QETH_MPC_TOKEN_LENGTH);
memcpy(QETH_ULP_SETUP_FILTER_TOKEN(iob->data),
&card->token.ulp_filter_r, QETH_MPC_TOKEN_LENGTH);
temp = __raw_devno_from_bus_id(CARD_DDEV_ID(card));
memcpy(QETH_ULP_SETUP_CUA(iob->data), &temp, 2);
temp = (card->info.cula << 8) + card->info.unit_addr2;
memcpy(QETH_ULP_SETUP_REAL_DEVADDR(iob->data), &temp, 2);
rc = qeth_send_control_data(card, ULP_SETUP_SIZE, iob,
qeth_ulp_setup_cb, NULL);
return rc;
}
static inline int
qeth_check_qdio_errors(struct qdio_buffer *buf, unsigned int qdio_error,
unsigned int siga_error, const char *dbftext)
{
if (qdio_error || siga_error) {
QETH_DBF_TEXT(trace, 2, dbftext);
QETH_DBF_TEXT(qerr, 2, dbftext);
QETH_DBF_TEXT_(qerr, 2, " F15=%02X",
buf->element[15].flags & 0xff);
QETH_DBF_TEXT_(qerr, 2, " F14=%02X",
buf->element[14].flags & 0xff);
QETH_DBF_TEXT_(qerr, 2, " qerr=%X", qdio_error);
QETH_DBF_TEXT_(qerr, 2, " serr=%X", siga_error);
return 1;
}
return 0;
}
static inline struct sk_buff *
qeth_get_skb(unsigned int length, struct qeth_hdr *hdr)
{
struct sk_buff* skb;
int add_len;
add_len = 0;
if (hdr->hdr.osn.id == QETH_HEADER_TYPE_OSN)
add_len = sizeof(struct qeth_hdr);
#ifdef CONFIG_QETH_VLAN
else
add_len = VLAN_HLEN;
#endif
skb = dev_alloc_skb(length + add_len);
if (skb && add_len)
skb_reserve(skb, add_len);
return skb;
}
static inline struct sk_buff *
qeth_get_next_skb(struct qeth_card *card, struct qdio_buffer *buffer,
struct qdio_buffer_element **__element, int *__offset,
struct qeth_hdr **hdr)
{
struct qdio_buffer_element *element = *__element;
int offset = *__offset;
struct sk_buff *skb = NULL;
int skb_len;
void *data_ptr;
int data_len;
QETH_DBF_TEXT(trace,6,"nextskb");
/* qeth_hdr must not cross element boundaries */
if (element->length < offset + sizeof(struct qeth_hdr)){
if (qeth_is_last_sbale(element))
return NULL;
element++;
offset = 0;
if (element->length < sizeof(struct qeth_hdr))
return NULL;
}
*hdr = element->addr + offset;
offset += sizeof(struct qeth_hdr);
if (card->options.layer2)
if (card->info.type == QETH_CARD_TYPE_OSN)
skb_len = (*hdr)->hdr.osn.pdu_length;
else
skb_len = (*hdr)->hdr.l2.pkt_length;
else
skb_len = (*hdr)->hdr.l3.length;
if (!skb_len)
return NULL;
if (card->options.fake_ll){
if(card->dev->type == ARPHRD_IEEE802_TR){
if (!(skb = qeth_get_skb(skb_len+QETH_FAKE_LL_LEN_TR, *hdr)))
goto no_mem;
skb_reserve(skb,QETH_FAKE_LL_LEN_TR);
} else {
if (!(skb = qeth_get_skb(skb_len+QETH_FAKE_LL_LEN_ETH, *hdr)))
goto no_mem;
skb_reserve(skb,QETH_FAKE_LL_LEN_ETH);
}
} else if (!(skb = qeth_get_skb(skb_len, *hdr)))
goto no_mem;
data_ptr = element->addr + offset;
while (skb_len) {
data_len = min(skb_len, (int)(element->length - offset));
if (data_len)
memcpy(skb_put(skb, data_len), data_ptr, data_len);
skb_len -= data_len;
if (skb_len){
if (qeth_is_last_sbale(element)){
QETH_DBF_TEXT(trace,4,"unexeob");
QETH_DBF_TEXT_(trace,4,"%s",CARD_BUS_ID(card));
QETH_DBF_TEXT(qerr,2,"unexeob");
QETH_DBF_TEXT_(qerr,2,"%s",CARD_BUS_ID(card));
QETH_DBF_HEX(misc,4,buffer,sizeof(*buffer));
dev_kfree_skb_any(skb);
card->stats.rx_errors++;
return NULL;
}
element++;
offset = 0;
data_ptr = element->addr;
} else {
offset += data_len;
}
}
*__element = element;
*__offset = offset;
return skb;
no_mem:
if (net_ratelimit()){
PRINT_WARN("No memory for packet received on %s.\n",
QETH_CARD_IFNAME(card));
QETH_DBF_TEXT(trace,2,"noskbmem");
QETH_DBF_TEXT_(trace,2,"%s",CARD_BUS_ID(card));
}
card->stats.rx_dropped++;
return NULL;
}
static inline __be16
qeth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
struct qeth_card *card;
struct ethhdr *eth;
QETH_DBF_TEXT(trace,6,"typtrans");
card = (struct qeth_card *)dev->priv;
#ifdef CONFIG_TR
if ((card->info.link_type == QETH_LINK_TYPE_HSTR) ||
(card->info.link_type == QETH_LINK_TYPE_LANE_TR))
return tr_type_trans(skb,dev);
#endif /* CONFIG_TR */
skb->mac.raw = skb->data;
skb_pull(skb, ETH_HLEN );
eth = eth_hdr(skb);
if (*eth->h_dest & 1) {
if (memcmp(eth->h_dest, dev->broadcast, ETH_ALEN) == 0)
skb->pkt_type = PACKET_BROADCAST;
else
skb->pkt_type = PACKET_MULTICAST;
} else if (memcmp(eth->h_dest, dev->dev_addr, ETH_ALEN))
skb->pkt_type = PACKET_OTHERHOST;
if (ntohs(eth->h_proto) >= 1536)
return eth->h_proto;
if (*(unsigned short *) (skb->data) == 0xFFFF)
return htons(ETH_P_802_3);
return htons(ETH_P_802_2);
}
static inline void
qeth_rebuild_skb_fake_ll_tr(struct qeth_card *card, struct sk_buff *skb,
struct qeth_hdr *hdr)
{
struct trh_hdr *fake_hdr;
struct trllc *fake_llc;
struct iphdr *ip_hdr;
QETH_DBF_TEXT(trace,5,"skbfktr");
skb->mac.raw = skb->data - QETH_FAKE_LL_LEN_TR;
/* this is a fake ethernet header */
fake_hdr = (struct trh_hdr *) skb->mac.raw;
/* the destination MAC address */
switch (skb->pkt_type){
case PACKET_MULTICAST:
switch (skb->protocol){
#ifdef CONFIG_QETH_IPV6
case __constant_htons(ETH_P_IPV6):
ndisc_mc_map((struct in6_addr *)
skb->data + QETH_FAKE_LL_V6_ADDR_POS,
fake_hdr->daddr, card->dev, 0);
break;
#endif /* CONFIG_QETH_IPV6 */
case __constant_htons(ETH_P_IP):
ip_hdr = (struct iphdr *)skb->data;
ip_tr_mc_map(ip_hdr->daddr, fake_hdr->daddr);
break;
default:
memcpy(fake_hdr->daddr, card->dev->dev_addr, TR_ALEN);
}
break;
case PACKET_BROADCAST:
memset(fake_hdr->daddr, 0xff, TR_ALEN);
break;
default:
memcpy(fake_hdr->daddr, card->dev->dev_addr, TR_ALEN);
}
/* the source MAC address */
if (hdr->hdr.l3.ext_flags & QETH_HDR_EXT_SRC_MAC_ADDR)
memcpy(fake_hdr->saddr, &hdr->hdr.l3.dest_addr[2], TR_ALEN);
else
memset(fake_hdr->saddr, 0, TR_ALEN);
fake_hdr->rcf=0;
fake_llc = (struct trllc*)&(fake_hdr->rcf);
fake_llc->dsap = EXTENDED_SAP;
fake_llc->ssap = EXTENDED_SAP;
fake_llc->llc = UI_CMD;
fake_llc->protid[0] = 0;
fake_llc->protid[1] = 0;
fake_llc->protid[2] = 0;
fake_llc->ethertype = ETH_P_IP;
}
static inline void
qeth_rebuild_skb_fake_ll_eth(struct qeth_card *card, struct sk_buff *skb,
struct qeth_hdr *hdr)
{
struct ethhdr *fake_hdr;
struct iphdr *ip_hdr;
QETH_DBF_TEXT(trace,5,"skbfketh");
skb->mac.raw = skb->data - QETH_FAKE_LL_LEN_ETH;
/* this is a fake ethernet header */
fake_hdr = (struct ethhdr *) skb->mac.raw;
/* the destination MAC address */
switch (skb->pkt_type){
case PACKET_MULTICAST:
switch (skb->protocol){
#ifdef CONFIG_QETH_IPV6
case __constant_htons(ETH_P_IPV6):
ndisc_mc_map((struct in6_addr *)
skb->data + QETH_FAKE_LL_V6_ADDR_POS,
fake_hdr->h_dest, card->dev, 0);
break;
#endif /* CONFIG_QETH_IPV6 */
case __constant_htons(ETH_P_IP):
ip_hdr = (struct iphdr *)skb->data;
ip_eth_mc_map(ip_hdr->daddr, fake_hdr->h_dest);
break;
default:
memcpy(fake_hdr->h_dest, card->dev->dev_addr, ETH_ALEN);
}
break;
case PACKET_BROADCAST:
memset(fake_hdr->h_dest, 0xff, ETH_ALEN);
break;
default:
memcpy(fake_hdr->h_dest, card->dev->dev_addr, ETH_ALEN);
}
/* the source MAC address */
if (hdr->hdr.l3.ext_flags & QETH_HDR_EXT_SRC_MAC_ADDR)
memcpy(fake_hdr->h_source, &hdr->hdr.l3.dest_addr[2], ETH_ALEN);
else
memset(fake_hdr->h_source, 0, ETH_ALEN);
/* the protocol */
fake_hdr->h_proto = skb->protocol;
}
static inline void
qeth_rebuild_skb_fake_ll(struct qeth_card *card, struct sk_buff *skb,
struct qeth_hdr *hdr)
{
if (card->dev->type == ARPHRD_IEEE802_TR)
qeth_rebuild_skb_fake_ll_tr(card, skb, hdr);
else
qeth_rebuild_skb_fake_ll_eth(card, skb, hdr);
}
static inline void
qeth_rebuild_skb_vlan(struct qeth_card *card, struct sk_buff *skb,
struct qeth_hdr *hdr)
{
#ifdef CONFIG_QETH_VLAN
u16 *vlan_tag;
if (hdr->hdr.l3.ext_flags &
(QETH_HDR_EXT_VLAN_FRAME | QETH_HDR_EXT_INCLUDE_VLAN_TAG)) {
vlan_tag = (u16 *) skb_push(skb, VLAN_HLEN);
*vlan_tag = (hdr->hdr.l3.ext_flags & QETH_HDR_EXT_VLAN_FRAME)?
hdr->hdr.l3.vlan_id : *((u16 *)&hdr->hdr.l3.dest_addr[12]);
*(vlan_tag + 1) = skb->protocol;
skb->protocol = __constant_htons(ETH_P_8021Q);
}
#endif /* CONFIG_QETH_VLAN */
}
static inline __u16
qeth_layer2_rebuild_skb(struct qeth_card *card, struct sk_buff *skb,
struct qeth_hdr *hdr)
{
unsigned short vlan_id = 0;
#ifdef CONFIG_QETH_VLAN
struct vlan_hdr *vhdr;
#endif
skb->pkt_type = PACKET_HOST;
skb->protocol = qeth_type_trans(skb, skb->dev);
if (card->options.checksum_type == NO_CHECKSUMMING)
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb->ip_summed = CHECKSUM_NONE;
#ifdef CONFIG_QETH_VLAN
if (hdr->hdr.l2.flags[2] & (QETH_LAYER2_FLAG_VLAN)) {
vhdr = (struct vlan_hdr *) skb->data;
skb->protocol =
__constant_htons(vhdr->h_vlan_encapsulated_proto);
vlan_id = hdr->hdr.l2.vlan_id;
skb_pull(skb, VLAN_HLEN);
}
#endif
*((__u32 *)skb->cb) = ++card->seqno.pkt_seqno;
return vlan_id;
}
static inline void
qeth_rebuild_skb(struct qeth_card *card, struct sk_buff *skb,
struct qeth_hdr *hdr)
{
#ifdef CONFIG_QETH_IPV6
if (hdr->hdr.l3.flags & QETH_HDR_PASSTHRU) {
skb->pkt_type = PACKET_HOST;
skb->protocol = qeth_type_trans(skb, card->dev);
return;
}
#endif /* CONFIG_QETH_IPV6 */
skb->protocol = htons((hdr->hdr.l3.flags & QETH_HDR_IPV6)? ETH_P_IPV6 :
ETH_P_IP);
switch (hdr->hdr.l3.flags & QETH_HDR_CAST_MASK){
case QETH_CAST_UNICAST:
skb->pkt_type = PACKET_HOST;
break;
case QETH_CAST_MULTICAST:
skb->pkt_type = PACKET_MULTICAST;
card->stats.multicast++;
break;
case QETH_CAST_BROADCAST:
skb->pkt_type = PACKET_BROADCAST;
card->stats.multicast++;
break;
case QETH_CAST_ANYCAST:
case QETH_CAST_NOCAST:
default:
skb->pkt_type = PACKET_HOST;
}
qeth_rebuild_skb_vlan(card, skb, hdr);
if (card->options.fake_ll)
qeth_rebuild_skb_fake_ll(card, skb, hdr);
else
skb->mac.raw = skb->data;
skb->ip_summed = card->options.checksum_type;
if (card->options.checksum_type == HW_CHECKSUMMING){
if ( (hdr->hdr.l3.ext_flags &
(QETH_HDR_EXT_CSUM_HDR_REQ |
QETH_HDR_EXT_CSUM_TRANSP_REQ)) ==
(QETH_HDR_EXT_CSUM_HDR_REQ |
QETH_HDR_EXT_CSUM_TRANSP_REQ) )
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb->ip_summed = SW_CHECKSUMMING;
}
}
static inline void
qeth_process_inbound_buffer(struct qeth_card *card,
struct qeth_qdio_buffer *buf, int index)
{
struct qdio_buffer_element *element;
struct sk_buff *skb;
struct qeth_hdr *hdr;
int offset;
int rxrc;
__u16 vlan_tag = 0;
/* get first element of current buffer */
element = (struct qdio_buffer_element *)&buf->buffer->element[0];
offset = 0;
#ifdef CONFIG_QETH_PERF_STATS
card->perf_stats.bufs_rec++;
#endif
while((skb = qeth_get_next_skb(card, buf->buffer, &element,
&offset, &hdr))) {
skb->dev = card->dev;
if (hdr->hdr.l2.id == QETH_HEADER_TYPE_LAYER2)
vlan_tag = qeth_layer2_rebuild_skb(card, skb, hdr);
else if (hdr->hdr.l3.id == QETH_HEADER_TYPE_LAYER3)
qeth_rebuild_skb(card, skb, hdr);
else { /*in case of OSN*/
skb_push(skb, sizeof(struct qeth_hdr));
memcpy(skb->data, hdr, sizeof(struct qeth_hdr));
}
/* is device UP ? */
if (!(card->dev->flags & IFF_UP)){
dev_kfree_skb_any(skb);
continue;
}
#ifdef CONFIG_QETH_VLAN
if (vlan_tag)
vlan_hwaccel_rx(skb, card->vlangrp, vlan_tag);
else
#endif
if (card->info.type == QETH_CARD_TYPE_OSN)
rxrc = card->osn_info.data_cb(skb);
else
rxrc = netif_rx(skb);
card->dev->last_rx = jiffies;
card->stats.rx_packets++;
card->stats.rx_bytes += skb->len;