#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <functional>
#include <vector>
#include <queue>
#include <deque>
#include <map>
#include <set>
#define nmax_def 110000
#define mod 1000000007
#define rep(i,n) for(int (i)=0;(i)<(n);(i)++)
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<bool> vb;
#define graph_from1 true
#define graph_tree false
#define graph_weight false
#define graph_digraph false

class Graph_func {
public:
	int Vnum;
	int Enum;
	vector<pii> edgelist;
	vl edgeweight;
	vector<vi> adjlist;
	vector<vl> adjlistweight;
	vi adjnum;

	vi adj_to_root;
	vector<int> DP;
	vl xdist;

	vb x_visited;
	vi x_prqarr;
	vb x_filled;

	void read_edge();
	void adjini();
	void visited_ini();
	void adjsort();
	vb graph_num_BFS(int BFS_ini, vb filled);
	int graph_num();
	void graph_BFS(ll BFS_ini);
	void graph_DFS(ll DFS_ini);
	void ReRooting(ll root);
	void xydist_tree(int x);   // ex. xydist_tree(3)
	void xydist(int x);   // ex. xydist(3, 0)
	void solve();
	ll diameter();
	int dim_num(int dim);

	bool xydist_def = false;
};

void Graph_func::read_edge() {
	int H, W;
	cin >> H >> W;
	Vnum = H * W;
	Enum = 0;
	vector<string> S(H);
	rep(i, H) {
		cin >> S[i];
	}
	rep(i, H) {
		rep(j, W) {
			if (i != 0) {
				if (S[i - 1][j] == '.' && S[i][j] == '.') {
					Enum++;
					pii edgetmp;
					edgetmp.first = (i - 1) * W + j;
					edgetmp.second = i * W + j;
					edgelist.push_back(edgetmp);
				}
			}
			if (j != 0) {
				if (S[i][j - 1] == '.' && S[i][j] == '.') {
					Enum++;
					pii edgetmp;
					edgetmp.first = i * W + j - 1;
					edgetmp.second = i * W + j;
					edgelist.push_back(edgetmp);
				}
			}
		}
	}


	edgeweight.resize(Enum);
	adjnum.resize(Vnum);
	xdist.resize(Vnum);
	adjlist.resize(Vnum);
	if (graph_weight)
		adjlistweight.resize(Vnum);
	adj_to_root.resize(Vnum, -1);
	DP.resize(Vnum, 0);
	return;
}

void Graph_func::adjini() {
	rep(i, Vnum)
		adjnum[i] = 0;
	rep(i, Enum) {
		int x = edgelist[i].first;
		int y = edgelist[i].second;
		adjlist[x].push_back(y);
		if (!graph_digraph)
			adjlist[y].push_back(x);
		if (graph_weight) {
			ll xyweight = edgeweight[i];
			adjlistweight[x].push_back(xyweight);
			if (!graph_digraph)
				adjlistweight[y].push_back(xyweight);
		}
		adjnum[x]++;
		if (!graph_digraph)
			adjnum[y]++;
	}
	x_visited.resize(Vnum);
	visited_ini();

	return;
}

void Graph_func::visited_ini() {
	rep(i, Vnum)
		x_visited[i] = false;
	x_prqarr.resize(0);
}

void Graph_func::xydist(int x) {  // ex. xydist(3), Dijkstra's algorithm
	priority_queue<pair<ll, int>, vector<pair<ll, int> >, greater<pair<ll, int> > > xdist_que; //Alive_vertices
	ll max_tmp = 100000;
	rep(i, Vnum)
		xdist[i] = max_tmp;
	xdist[x] = 0;
	pair<ll, int> xdist_que_tmp;
	xdist_que_tmp.first = 0;
	xdist_que_tmp.second = x;
	xdist_que.push(xdist_que_tmp);
	while (!xdist_que.empty()) {
		xdist_que_tmp = xdist_que.top();
		xdist_que.pop();
		ll xdist_tmp = xdist_que_tmp.first;
		int x_tmp = xdist_que_tmp.second;
		if (xdist[x_tmp] == xdist_tmp) {
			rep(i, adjnum[x_tmp]) {
				int x_new = adjlist[x_tmp][i];
				ll dist_diff;
				if (graph_weight)
					dist_diff = adjlistweight[x_tmp][i];
				else dist_diff = 1;
				if (xdist[x_new] > xdist[x_tmp] + dist_diff) {
					xdist[x_new] = xdist[x_tmp] + dist_diff;
					xdist_que_tmp.first = xdist[x_new];
					xdist_que_tmp.second = x_new;
					xdist_que.push(xdist_que_tmp);
				}
			}
		}
	}
}

ll Graph_func::diameter() {
	ll ans = 0;
	rep(i, Vnum) {
		visited_ini();
		xydist(i);
		rep(j, Vnum) {
			if (xdist[j] != 100000) {
				ans = max(ans, xdist[j]);
			}
		}
	}
	return ans;
}

void Graph_func::solve() {
	ll ans = diameter();
	cout << ans << endl;
}

int main(void) {
	Graph_func graph;
	ll ans = 0;
	graph.read_edge();
	graph.adjini();
	ll Vnum = graph.Vnum;
	ll Enum = graph.Enum;
	graph.solve();
	return 0;
}

Measured memory_bytes: 256