aboutsummaryrefslogblamecommitdiffstats
path: root/net/rds/message.c
blob: ca50a8ec97427ecc04ca65b42ddb2a4f31d117b0 (plain) (tree)



















































                                                                            
                                      









































                                                                                  
                                   















                                                                              
                                               
























                                                                      
                                             










































































                                                                                               
                                                       






























































































































































































                                                                                            
                                        
 
/*
 * Copyright (c) 2006 Oracle.  All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include <linux/kernel.h>

#include "rds.h"
#include "rdma.h"

static DECLARE_WAIT_QUEUE_HEAD(rds_message_flush_waitq);

static unsigned int	rds_exthdr_size[__RDS_EXTHDR_MAX] = {
[RDS_EXTHDR_NONE]	= 0,
[RDS_EXTHDR_VERSION]	= sizeof(struct rds_ext_header_version),
[RDS_EXTHDR_RDMA]	= sizeof(struct rds_ext_header_rdma),
[RDS_EXTHDR_RDMA_DEST]	= sizeof(struct rds_ext_header_rdma_dest),
};


void rds_message_addref(struct rds_message *rm)
{
	rdsdebug("addref rm %p ref %d\n", rm, atomic_read(&rm->m_refcount));
	atomic_inc(&rm->m_refcount);
}
EXPORT_SYMBOL_GPL(rds_message_addref);

/*
 * This relies on dma_map_sg() not touching sg[].page during merging.
 */
static void rds_message_purge(struct rds_message *rm)
{
	unsigned long i;

	if (unlikely(test_bit(RDS_MSG_PAGEVEC, &rm->m_flags)))
		return;

	for (i = 0; i < rm->m_nents; i++) {
		rdsdebug("putting data page %p\n", (void *)sg_page(&rm->m_sg[i]));
		/* XXX will have to put_page for page refs */
		__free_page(sg_page(&rm->m_sg[i]));
	}
	rm->m_nents = 0;

	if (rm->m_rdma_op)
		rds_rdma_free_op(rm->m_rdma_op);
	if (rm->m_rdma_mr)
		rds_mr_put(rm->m_rdma_mr);
}

void rds_message_inc_purge(struct rds_incoming *inc)
{
	struct rds_message *rm = container_of(inc, struct rds_message, m_inc);
	rds_message_purge(rm);
}

void rds_message_put(struct rds_message *rm)
{
	rdsdebug("put rm %p ref %d\n", rm, atomic_read(&rm->m_refcount));

	if (atomic_dec_and_test(&rm->m_refcount)) {
		BUG_ON(!list_empty(&rm->m_sock_item));
		BUG_ON(!list_empty(&rm->m_conn_item));
		rds_message_purge(rm);

		kfree(rm);
	}
}
EXPORT_SYMBOL_GPL(rds_message_put);

void rds_message_inc_free(struct rds_incoming *inc)
{
	struct rds_message *rm = container_of(inc, struct rds_message, m_inc);
	rds_message_put(rm);
}

void rds_message_populate_header(struct rds_header *hdr, __be16 sport,
				 __be16 dport, u64 seq)
{
	hdr->h_flags = 0;
	hdr->h_sport = sport;
	hdr->h_dport = dport;
	hdr->h_sequence = cpu_to_be64(seq);
	hdr->h_exthdr[0] = RDS_EXTHDR_NONE;
}
EXPORT_SYMBOL_GPL(rds_message_populate_header);

int rds_message_add_extension(struct rds_header *hdr,
		unsigned int type, const void *data, unsigned int len)
{
	unsigned int ext_len = sizeof(u8) + len;
	unsigned char *dst;

	/* For now, refuse to add more than one extension header */
	if (hdr->h_exthdr[0] != RDS_EXTHDR_NONE)
		return 0;

	if (type >= __RDS_EXTHDR_MAX
	 || len != rds_exthdr_size[type])
		return 0;

	if (ext_len >= RDS_HEADER_EXT_SPACE)
		return 0;
	dst = hdr->h_exthdr;

	*dst++ = type;
	memcpy(dst, data, len);

	dst[len] = RDS_EXTHDR_NONE;
	return 1;
}
EXPORT_SYMBOL_GPL(rds_message_add_extension);

/*
 * If a message has extension headers, retrieve them here.
 * Call like this:
 *
 * unsigned int pos = 0;
 *
 * while (1) {
 *	buflen = sizeof(buffer);
 *	type = rds_message_next_extension(hdr, &pos, buffer, &buflen);
 *	if (type == RDS_EXTHDR_NONE)
 *		break;
 *	...
 * }
 */
int rds_message_next_extension(struct rds_header *hdr,
		unsigned int *pos, void *buf, unsigned int *buflen)
{
	unsigned int offset, ext_type, ext_len;
	u8 *src = hdr->h_exthdr;

	offset = *pos;
	if (offset >= RDS_HEADER_EXT_SPACE)
		goto none;

	/* Get the extension type and length. For now, the
	 * length is implied by the extension type. */
	ext_type = src[offset++];

	if (ext_type == RDS_EXTHDR_NONE || ext_type >= __RDS_EXTHDR_MAX)
		goto none;
	ext_len = rds_exthdr_size[ext_type];
	if (offset + ext_len > RDS_HEADER_EXT_SPACE)
		goto none;

	*pos = offset + ext_len;
	if (ext_len < *buflen)
		*buflen = ext_len;
	memcpy(buf, src + offset, *buflen);
	return ext_type;

none:
	*pos = RDS_HEADER_EXT_SPACE;
	*buflen = 0;
	return RDS_EXTHDR_NONE;
}

int rds_message_add_version_extension(struct rds_header *hdr, unsigned int version)
{
	struct rds_ext_header_version ext_hdr;

	ext_hdr.h_version = cpu_to_be32(version);
	return rds_message_add_extension(hdr, RDS_EXTHDR_VERSION, &ext_hdr, sizeof(ext_hdr));
}

int rds_message_get_version_extension(struct rds_header *hdr, unsigned int *version)
{
	struct rds_ext_header_version ext_hdr;
	unsigned int pos = 0, len = sizeof(ext_hdr);

	/* We assume the version extension is the only one present */
	if (rds_message_next_extension(hdr, &pos, &ext_hdr, &len) != RDS_EXTHDR_VERSION)
		return 0;
	*version = be32_to_cpu(ext_hdr.h_version);
	return 1;
}

int rds_message_add_rdma_dest_extension(struct rds_header *hdr, u32 r_key, u32 offset)
{
	struct rds_ext_header_rdma_dest ext_hdr;

	ext_hdr.h_rdma_rkey = cpu_to_be32(r_key);
	ext_hdr.h_rdma_offset = cpu_to_be32(offset);
	return rds_message_add_extension(hdr, RDS_EXTHDR_RDMA_DEST, &ext_hdr, sizeof(ext_hdr));
}
EXPORT_SYMBOL_GPL(rds_message_add_rdma_dest_extension);

struct rds_message *rds_message_alloc(unsigned int nents, gfp_t gfp)
{
	struct rds_message *rm;

	rm = kzalloc(sizeof(struct rds_message) +
		     (nents * sizeof(struct scatterlist)), gfp);
	if (!rm)
		goto out;

	if (nents)
		sg_init_table(rm->m_sg, nents);
	atomic_set(&rm->m_refcount, 1);
	INIT_LIST_HEAD(&rm->m_sock_item);
	INIT_LIST_HEAD(&rm->m_conn_item);
	spin_lock_init(&rm->m_rs_lock);

out:
	return rm;
}

struct rds_message *rds_message_map_pages(unsigned long *page_addrs, unsigned int total_len)
{
	struct rds_message *rm;
	unsigned int i;

	rm = rds_message_alloc(ceil(total_len, PAGE_SIZE), GFP_KERNEL);
	if (rm == NULL)
		return ERR_PTR(-ENOMEM);

	set_bit(RDS_MSG_PAGEVEC, &rm->m_flags);
	rm->m_inc.i_hdr.h_len = cpu_to_be32(total_len);
	rm->m_nents = ceil(total_len, PAGE_SIZE);

	for (i = 0; i < rm->m_nents; ++i) {
		sg_set_page(&rm->m_sg[i],
				virt_to_page(page_addrs[i]),
				PAGE_SIZE, 0);
	}

	return rm;
}

struct rds_message *rds_message_copy_from_user(struct iovec *first_iov,
					       size_t total_len)
{
	unsigned long to_copy;
	unsigned long iov_off;
	unsigned long sg_off;
	struct rds_message *rm;
	struct iovec *iov;
	struct scatterlist *sg;
	int ret;

	rm = rds_message_alloc(ceil(total_len, PAGE_SIZE), GFP_KERNEL);
	if (rm == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	rm->m_inc.i_hdr.h_len = cpu_to_be32(total_len);

	/*
	 * now allocate and copy in the data payload.
	 */
	sg = rm->m_sg;
	iov = first_iov;
	iov_off = 0;
	sg_off = 0; /* Dear gcc, sg->page will be null from kzalloc. */

	while (total_len) {
		if (sg_page(sg) == NULL) {
			ret = rds_page_remainder_alloc(sg, total_len,
						       GFP_HIGHUSER);
			if (ret)
				goto out;
			rm->m_nents++;
			sg_off = 0;
		}

		while (iov_off == iov->iov_len) {
			iov_off = 0;
			iov++;
		}

		to_copy = min(iov->iov_len - iov_off, sg->length - sg_off);
		to_copy = min_t(size_t, to_copy, total_len);

		rdsdebug("copying %lu bytes from user iov [%p, %zu] + %lu to "
			 "sg [%p, %u, %u] + %lu\n",
			 to_copy, iov->iov_base, iov->iov_len, iov_off,
			 (void *)sg_page(sg), sg->offset, sg->length, sg_off);

		ret = rds_page_copy_from_user(sg_page(sg), sg->offset + sg_off,
					      iov->iov_base + iov_off,
					      to_copy);
		if (ret)
			goto out;

		iov_off += to_copy;
		total_len -= to_copy;
		sg_off += to_copy;

		if (sg_off == sg->length)
			sg++;
	}

	ret = 0;
out:
	if (ret) {
		if (rm)
			rds_message_put(rm);
		rm = ERR_PTR(ret);
	}
	return rm;
}

int rds_message_inc_copy_to_user(struct rds_incoming *inc,
				 struct iovec *first_iov, size_t size)
{
	struct rds_message *rm;
	struct iovec *iov;
	struct scatterlist *sg;
	unsigned long to_copy;
	unsigned long iov_off;
	unsigned long vec_off;
	int copied;
	int ret;
	u32 len;

	rm = container_of(inc, struct rds_message, m_inc);
	len = be32_to_cpu(rm->m_inc.i_hdr.h_len);

	iov = first_iov;
	iov_off = 0;
	sg = rm->m_sg;
	vec_off = 0;
	copied = 0;

	while (copied < size && copied < len) {
		while (iov_off == iov->iov_len) {
			iov_off = 0;
			iov++;
		}

		to_copy = min(iov->iov_len - iov_off, sg->length - vec_off);
		to_copy = min_t(size_t, to_copy, size - copied);
		to_copy = min_t(unsigned long, to_copy, len - copied);

		rdsdebug("copying %lu bytes to user iov [%p, %zu] + %lu to "
			 "sg [%p, %u, %u] + %lu\n",
			 to_copy, iov->iov_base, iov->iov_len, iov_off,
			 sg_page(sg), sg->offset, sg->length, vec_off);

		ret = rds_page_copy_to_user(sg_page(sg), sg->offset + vec_off,
					    iov->iov_base + iov_off,
					    to_copy);
		if (ret) {
			copied = ret;
			break;
		}

		iov_off += to_copy;
		vec_off += to_copy;
		copied += to_copy;

		if (vec_off == sg->length) {
			vec_off = 0;
			sg++;
		}
	}

	return copied;
}

/*
 * If the message is still on the send queue, wait until the transport
 * is done with it. This is particularly important for RDMA operations.
 */
void rds_message_wait(struct rds_message *rm)
{
	wait_event(rds_message_flush_waitq,
			!test_bit(RDS_MSG_MAPPED, &rm->m_flags));
}

void rds_message_unmapped(struct rds_message *rm)
{
	clear_bit(RDS_MSG_MAPPED, &rm->m_flags);
	if (waitqueue_active(&rds_message_flush_waitq))
		wake_up(&rds_message_flush_waitq);
}
EXPORT_SYMBOL_GPL(rds_message_unmapped);

ass="hl opt">, piix4_smba); return piix4_smba; } static int piix4_transaction(struct i2c_adapter *piix4_adapter) { struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter); unsigned short piix4_smba = adapdata->smba; int temp; int result = 0; int timeout = 0; dev_dbg(&piix4_adapter->dev, "Transaction (pre): CNT=%02x, CMD=%02x, " "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); /* Make sure the SMBus host is ready to start transmitting */ if ((temp = inb_p(SMBHSTSTS)) != 0x00) { dev_dbg(&piix4_adapter->dev, "SMBus busy (%02x). " "Resetting...\n", temp); outb_p(temp, SMBHSTSTS); if ((temp = inb_p(SMBHSTSTS)) != 0x00) { dev_err(&piix4_adapter->dev, "Failed! (%02x)\n", temp); return -EBUSY; } else { dev_dbg(&piix4_adapter->dev, "Successful!\n"); } } /* start the transaction by setting bit 6 */ outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT); /* We will always wait for a fraction of a second! (See PIIX4 docs errata) */ if (srvrworks_csb5_delay) /* Extra delay for SERVERWORKS_CSB5 */ msleep(2); else msleep(1); while ((++timeout < MAX_TIMEOUT) && ((temp = inb_p(SMBHSTSTS)) & 0x01)) msleep(1); /* If the SMBus is still busy, we give up */ if (timeout == MAX_TIMEOUT) { dev_err(&piix4_adapter->dev, "SMBus Timeout!\n"); result = -ETIMEDOUT; } if (temp & 0x10) { result = -EIO; dev_err(&piix4_adapter->dev, "Error: Failed bus transaction\n"); } if (temp & 0x08) { result = -EIO; dev_dbg(&piix4_adapter->dev, "Bus collision! SMBus may be " "locked until next hard reset. (sorry!)\n"); /* Clock stops and slave is stuck in mid-transmission */ } if (temp & 0x04) { result = -ENXIO; dev_dbg(&piix4_adapter->dev, "Error: no response!\n"); } if (inb_p(SMBHSTSTS) != 0x00) outb_p(inb(SMBHSTSTS), SMBHSTSTS); if ((temp = inb_p(SMBHSTSTS)) != 0x00) { dev_err(&piix4_adapter->dev, "Failed reset at end of " "transaction (%02x)\n", temp); } dev_dbg(&piix4_adapter->dev, "Transaction (post): CNT=%02x, CMD=%02x, " "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), inb_p(SMBHSTDAT1)); return result; } /* Return negative errno on error. */ static s32 piix4_access(struct i2c_adapter * adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data * data) { struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(adap); unsigned short piix4_smba = adapdata->smba; int i, len; int status; switch (size) { case I2C_SMBUS_QUICK: outb_p((addr << 1) | read_write, SMBHSTADD); size = PIIX4_QUICK; break; case I2C_SMBUS_BYTE: outb_p((addr << 1) | read_write, SMBHSTADD); if (read_write == I2C_SMBUS_WRITE) outb_p(command, SMBHSTCMD); size = PIIX4_BYTE; break; case I2C_SMBUS_BYTE_DATA: outb_p((addr << 1) | read_write, SMBHSTADD); outb_p(command, SMBHSTCMD); if (read_write == I2C_SMBUS_WRITE) outb_p(data->byte, SMBHSTDAT0); size = PIIX4_BYTE_DATA; break; case I2C_SMBUS_WORD_DATA: outb_p((addr << 1) | read_write, SMBHSTADD); outb_p(command, SMBHSTCMD); if (read_write == I2C_SMBUS_WRITE) { outb_p(data->word & 0xff, SMBHSTDAT0); outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); } size = PIIX4_WORD_DATA; break; case I2C_SMBUS_BLOCK_DATA: outb_p((addr << 1) | read_write, SMBHSTADD); outb_p(command, SMBHSTCMD); if (read_write == I2C_SMBUS_WRITE) { len = data->block[0]; if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) return -EINVAL; outb_p(len, SMBHSTDAT0); inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ for (i = 1; i <= len; i++) outb_p(data->block[i], SMBBLKDAT); } size = PIIX4_BLOCK_DATA; break; default: dev_warn(&adap->dev, "Unsupported transaction %d\n", size); return -EOPNOTSUPP; } outb_p((size & 0x1C) + (ENABLE_INT9 & 1), SMBHSTCNT); status = piix4_transaction(adap); if (status) return status; if ((read_write == I2C_SMBUS_WRITE) || (size == PIIX4_QUICK)) return 0; switch (size) { case PIIX4_BYTE: case PIIX4_BYTE_DATA: data->byte = inb_p(SMBHSTDAT0); break; case PIIX4_WORD_DATA: data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); break; case PIIX4_BLOCK_DATA: data->block[0] = inb_p(SMBHSTDAT0); if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX) return -EPROTO; inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ for (i = 1; i <= data->block[0]; i++) data->block[i] = inb_p(SMBBLKDAT); break; } return 0; } /* * Handles access to multiple SMBus ports on the SB800. * The port is selected by bits 2:1 of the smb_en register (0x2c). * Returns negative errno on error. * * Note: The selected port must be returned to the initial selection to avoid * problems on certain systems. */ static s32 piix4_access_sb800(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data) { struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(adap); u8 smba_en_lo; u8 port; int retval; mutex_lock(&piix4_mutex_sb800); outb_p(piix4_port_sel_sb800, SB800_PIIX4_SMB_IDX); smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1); port = adapdata->port; if ((smba_en_lo & SB800_PIIX4_PORT_IDX_MASK) != (port << 1)) outb_p((smba_en_lo & ~SB800_PIIX4_PORT_IDX_MASK) | (port << 1), SB800_PIIX4_SMB_IDX + 1); retval = piix4_access(adap, addr, flags, read_write, command, size, data); outb_p(smba_en_lo, SB800_PIIX4_SMB_IDX + 1); mutex_unlock(&piix4_mutex_sb800); return retval; } static u32 piix4_func(struct i2c_adapter *adapter) { return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_BLOCK_DATA; } static const struct i2c_algorithm smbus_algorithm = { .smbus_xfer = piix4_access, .functionality = piix4_func, }; static const struct i2c_algorithm piix4_smbus_algorithm_sb800 = { .smbus_xfer = piix4_access_sb800, .functionality = piix4_func, }; static const struct pci_device_id piix4_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3) }, { PCI_DEVICE(PCI_VENDOR_ID_EFAR, PCI_DEVICE_ID_EFAR_SLC90E66_3) }, { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP200_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP300_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_IXP400_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_HUDSON2_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_KERNCZ_SMBUS) }, { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_OSB4) }, { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_CSB5) }, { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_CSB6) }, { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_HT1000SB) }, { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_HT1100LD) }, { 0, } }; MODULE_DEVICE_TABLE (pci, piix4_ids); static struct i2c_adapter *piix4_main_adapters[PIIX4_MAX_ADAPTERS]; static struct i2c_adapter *piix4_aux_adapter; static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba, bool sb800_main, unsigned short port, const char *name, struct i2c_adapter **padap) { struct i2c_adapter *adap; struct i2c_piix4_adapdata *adapdata; int retval; adap = kzalloc(sizeof(*adap), GFP_KERNEL); if (adap == NULL) { release_region(smba, SMBIOSIZE); return -ENOMEM; } adap->owner = THIS_MODULE; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->algo = sb800_main ? &piix4_smbus_algorithm_sb800 : &smbus_algorithm; adapdata = kzalloc(sizeof(*adapdata), GFP_KERNEL); if (adapdata == NULL) { kfree(adap); release_region(smba, SMBIOSIZE); return -ENOMEM; } adapdata->smba = smba; adapdata->sb800_main = sb800_main; adapdata->port = port; /* set up the sysfs linkage to our parent device */ adap->dev.parent = &dev->dev; snprintf(adap->name, sizeof(adap->name), "SMBus PIIX4 adapter%s at %04x", name, smba); i2c_set_adapdata(adap, adapdata); retval = i2c_add_adapter(adap); if (retval) { dev_err(&dev->dev, "Couldn't register adapter!\n"); kfree(adapdata); kfree(adap); release_region(smba, SMBIOSIZE); return retval; } *padap = adap; return 0; } static int piix4_add_adapters_sb800(struct pci_dev *dev, unsigned short smba) { struct i2c_piix4_adapdata *adapdata; int port; int retval; for (port = 0; port < PIIX4_MAX_ADAPTERS; port++) { retval = piix4_add_adapter(dev, smba, true, port, piix4_main_port_names_sb800[port], &piix4_main_adapters[port]); if (retval < 0) goto error; } return retval; error: dev_err(&dev->dev, "Error setting up SB800 adapters. Unregistering!\n"); while (--port >= 0) { adapdata = i2c_get_adapdata(piix4_main_adapters[port]); if (adapdata->smba) { i2c_del_adapter(piix4_main_adapters[port]); kfree(adapdata); kfree(piix4_main_adapters[port]); piix4_main_adapters[port] = NULL; } } return retval; } static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id) { int retval; bool is_sb800 = false; if ((dev->vendor == PCI_VENDOR_ID_ATI && dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS && dev->revision >= 0x40) || dev->vendor == PCI_VENDOR_ID_AMD) { is_sb800 = true; if (!request_region(SB800_PIIX4_SMB_IDX, 2, "smba_idx")) { dev_err(&dev->dev, "SMBus base address index region 0x%x already in use!\n", SB800_PIIX4_SMB_IDX); return -EBUSY; } /* base address location etc changed in SB800 */ retval = piix4_setup_sb800(dev, id, 0); if (retval < 0) { release_region(SB800_PIIX4_SMB_IDX, 2); return retval; } /* * Try to register multiplexed main SMBus adapter, * give up if we can't */ retval = piix4_add_adapters_sb800(dev, retval); if (retval < 0) { release_region(SB800_PIIX4_SMB_IDX, 2); return retval; } } else { retval = piix4_setup(dev, id); if (retval < 0) return retval; /* Try to register main SMBus adapter, give up if we can't */ retval = piix4_add_adapter(dev, retval, false, 0, "", &piix4_main_adapters[0]); if (retval < 0) return retval; } /* Check for auxiliary SMBus on some AMD chipsets */ retval = -ENODEV; if (dev->vendor == PCI_VENDOR_ID_ATI && dev->device == PCI_DEVICE_ID_ATI_SBX00_SMBUS) { if (dev->revision < 0x40) { retval = piix4_setup_aux(dev, id, 0x58); } else { /* SB800 added aux bus too */ retval = piix4_setup_sb800(dev, id, 1); } } if (dev->vendor == PCI_VENDOR_ID_AMD && dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS) { retval = piix4_setup_sb800(dev, id, 1); } if (retval > 0) { /* Try to add the aux adapter if it exists, * piix4_add_adapter will clean up if this fails */ piix4_add_adapter(dev, retval, false, 0, is_sb800 ? piix4_aux_port_name_sb800 : "", &piix4_aux_adapter); } return 0; } static void piix4_adap_remove(struct i2c_adapter *adap) { struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(adap); if (adapdata->smba) { i2c_del_adapter(adap); if (adapdata->port == 0) { release_region(adapdata->smba, SMBIOSIZE); if (adapdata->sb800_main) release_region(SB800_PIIX4_SMB_IDX, 2); } kfree(adapdata); kfree(adap); } } static void piix4_remove(struct pci_dev *dev) { int port = PIIX4_MAX_ADAPTERS; while (--port >= 0) { if (piix4_main_adapters[port]) { piix4_adap_remove(piix4_main_adapters[port]); piix4_main_adapters[port] = NULL; } } if (piix4_aux_adapter) { piix4_adap_remove(piix4_aux_adapter); piix4_aux_adapter = NULL; } } static struct pci_driver piix4_driver = { .name = "piix4_smbus", .id_table = piix4_ids, .probe = piix4_probe, .remove = piix4_remove, }; module_pci_driver(piix4_driver); MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and " "Philip Edelbrock <phil@netroedge.com>"); MODULE_DESCRIPTION("PIIX4 SMBus driver"); MODULE_LICENSE("GPL");